diff options
author | Cian Jinks <cjinks99@gmail.com> | 2021-09-23 04:23:44 +0300 |
---|---|---|
committer | Howard Trickey <howard.trickey@gmail.com> | 2021-09-23 04:23:44 +0300 |
commit | 6e77afe6ec7b6a73f218f1fef264758abcbc778a (patch) | |
tree | 350b49ce287b8e32518ed099895588041bc48d93 /source/blender/editors/mesh | |
parent | a78d3c5261b55ba2a63d550aafada518a0f88fbe (diff) |
Applying patch D12600, GSOC Knife Tools branch
This adds constrained angle mode improvements,
snapping to global and local orientation,
visible distance and angle measurements,
undo capability,
x-ray mode,
multi-object edit mode.
See https://developer.blender.org/D12600 for more details.
Note: this project moved some of the default keymappings
around a bit, as discussed with users in the thread
https://devtalk.blender.org/t/gsoc-2021-knife-tool-improvements-feedback/19047
We'll change the manual documentation in the next couple of days.
Diffstat (limited to 'source/blender/editors/mesh')
-rw-r--r-- | source/blender/editors/mesh/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/mesh/editmesh_knife.c | 2785 | ||||
-rw-r--r-- | source/blender/editors/mesh/editmesh_knife_project.c | 2 | ||||
-rw-r--r-- | source/blender/editors/mesh/mesh_intern.h | 3 |
4 files changed, 2247 insertions, 544 deletions
diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt index 35bf295a678..4ad2e57d266 100644 --- a/source/blender/editors/mesh/CMakeLists.txt +++ b/source/blender/editors/mesh/CMakeLists.txt @@ -18,6 +18,7 @@ set(INC ../include ../uvedit + ../../blenfont ../../blenkernel ../../blenlib ../../blentranslation diff --git a/source/blender/editors/mesh/editmesh_knife.c b/source/blender/editors/mesh/editmesh_knife.c index 3e3593d18fd..eee4aec7459 100644 --- a/source/blender/editors/mesh/editmesh_knife.c +++ b/source/blender/editors/mesh/editmesh_knife.c @@ -29,6 +29,8 @@ #include "MEM_guardedalloc.h" +#include "BLF_api.h" + #include "BLI_alloca.h" #include "BLI_array.h" #include "BLI_linklist.h" @@ -36,6 +38,7 @@ #include "BLI_math.h" #include "BLI_memarena.h" #include "BLI_smallhash.h" +#include "BLI_stack.h" #include "BLI_string.h" #include "BLT_translation.h" @@ -44,15 +47,20 @@ #include "BKE_context.h" #include "BKE_editmesh.h" #include "BKE_editmesh_bvh.h" +#include "BKE_layer.h" #include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_unit.h" #include "GPU_immediate.h" #include "GPU_matrix.h" #include "GPU_state.h" #include "ED_mesh.h" +#include "ED_numinput.h" #include "ED_screen.h" #include "ED_space_api.h" +#include "ED_transform.h" #include "ED_view3d.h" #include "WM_api.h" @@ -69,15 +77,15 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" -#include "mesh_intern.h" /* own include */ +#include "mesh_intern.h" /* Own include. */ -/* detect isolated holes and fill them */ +/* Detect isolated holes and fill them. */ #define USE_NET_ISLAND_CONNECT -#define KMAXDIST (10 * U.dpi_fac) /* max mouse distance from edge before not detecting it */ +#define KMAXDIST (10 * U.dpi_fac) /* Max mouse distance from edge before not detecting it. */ -/* WARNING: knife float precision is fragile: - * be careful before making changes here see: (T43229, T42864, T42459, T41164). +/* WARNING: Knife float precision is fragile: + * Be careful before making changes here see: (T43229, T42864, T42459, T41164). */ #define KNIFE_FLT_EPS 0.00001f #define KNIFE_FLT_EPS_SQUARED (KNIFE_FLT_EPS * KNIFE_FLT_EPS) @@ -87,24 +95,37 @@ #define KNIFE_FLT_EPS_PX_EDGE 0.05f #define KNIFE_FLT_EPS_PX_FACE 0.05f +#define KNIFE_DEFAULT_ANGLE_SNAPPING_INCREMENT 30.0f +#define KNIFE_MIN_ANGLE_SNAPPING_INCREMENT 0.0f +#define KNIFE_MAX_ANGLE_SNAPPING_INCREMENT 90.0f + typedef struct KnifeColors { uchar line[3]; uchar edge[3]; + uchar edge_extra[3]; uchar curpoint[3]; uchar curpoint_a[4]; uchar point[3]; uchar point_a[4]; + uchar xaxis[3]; + uchar yaxis[3]; + uchar zaxis[3]; + uchar axis_extra[3]; } KnifeColors; -/* knifetool operator */ +/* Knifetool Operator. */ typedef struct KnifeVert { - BMVert *v; /* non-NULL if this is an original vert */ + Object *ob; + uint base_index; + BMVert *v; /* Non-NULL if this is an original vert. */ ListBase edges; ListBase faces; float co[3], cageco[3]; bool is_face, in_space; - bool is_cut; /* along a cut created by user input (will draw too) */ + bool is_cut; /* Along a cut created by user input (will draw too). */ + bool is_invalid; + bool is_splitting; /* Created when an edge was split. */ } KnifeVert; typedef struct Ref { @@ -114,19 +135,21 @@ typedef struct Ref { typedef struct KnifeEdge { KnifeVert *v1, *v2; - BMFace *basef; /* face to restrict face fill to */ + BMFace *basef; /* Face to restrict face fill to. */ ListBase faces; - BMEdge *e /* , *e_old */; /* non-NULL if this is an original edge */ - bool is_cut; /* along a cut created by user input (will draw too) */ + BMEdge *e; /* Non-NULL if this is an original edge. */ + bool is_cut; /* Along a cut created by user input (will draw too). */ + bool is_invalid; + int splits; /* Number of times this edge has been split. */ } KnifeEdge; typedef struct KnifeLineHit { float hit[3], cagehit[3]; - float schit[2]; /* screen coordinates for cagehit */ - float l; /* lambda along cut line */ - float perc; /* lambda along hit line */ - float m; /* depth front-to-back */ + float schit[2]; /* Screen coordinates for cagehit. */ + float l; /* Lambda along cut line. */ + float perc; /* Lambda along hit line. */ + float m; /* Depth front-to-back. */ /* Exactly one of kfe, v, or f should be non-NULL, * saying whether cut line crosses and edge, @@ -134,6 +157,8 @@ typedef struct KnifeLineHit { KnifeEdge *kfe; KnifeVert *v; BMFace *f; + Object *ob; + uint base_index; } KnifeLineHit; typedef struct KnifePosData { @@ -146,30 +171,61 @@ typedef struct KnifePosData { KnifeVert *vert; KnifeEdge *edge; BMFace *bmface; + Object *ob; /* Object of the vert, edge or bmface. */ + uint base_index; - /** When true, the cursor isn't over a face. */ + /* When true, the cursor isn't over a face. */ bool is_space; - float mval[2]; /* mouse screen position (may be non-integral if snapped to something) */ + float mval[2]; /* Mouse screen position (may be non-integral if snapped to something). */ } KnifePosData; +typedef struct KnifeMeasureData { + float cage[3]; + float mval[2]; + bool is_stored; + float corr_prev_cage[3]; /* "knife_start_cut" updates prev.cage breaking angle calculations, + * store correct version. */ +} KnifeMeasureData; + +typedef struct KnifeUndoFrame { + int cuts; /* Line hits cause multiple edges/cuts to be created at once. */ + int splits; /* Number of edges split. */ + KnifePosData pos; /* Store previous KnifePosData. */ + KnifeMeasureData mdata; + +} KnifeUndoFrame; + +typedef struct KnifeBVH { + BVHTree *tree; /* Knife Custom BVH Tree. */ + BMLoop *(*looptris)[3]; /* Used by #knife_bvh_raycast_cb to store the intersecting looptri. */ + float uv[2]; /* Used by #knife_bvh_raycast_cb to store the intersecting uv. */ + uint base_index; + + /* Use #bm_ray_cast_cb_elem_not_in_face_check. */ + bool (*filter_cb)(BMFace *f, void *userdata); + void *filter_data; + +} KnifeBVH; + /* struct for properties used while drawing */ typedef struct KnifeTool_OpData { - ARegion *region; /* region that knifetool was activated in */ - void *draw_handle; /* for drawing preview loop */ - ViewContext vc; /* NOTE: _don't_ use 'mval', instead use the one we define below. */ - float mval[2]; /* mouse value with snapping applied */ - // bContext *C; + ARegion *region; /* Region that knifetool was activated in. */ + void *draw_handle; /* For drawing preview loop. */ + ViewContext vc; /* Note: _don't_ use 'mval', instead use the one we define below. */ + float mval[2]; /* Mouse value with snapping applied. */ Scene *scene; - Object *ob; - BMEditMesh *em; + + /* Used for swapping current object when in multi-object edit mode. */ + Object **objects; + uint objects_len; MemArena *arena; - /* reused for edge-net filling */ + /* Reused for edge-net filling. */ struct { - /* cleared each use */ + /* Cleared each use. */ GSet *edge_visit; #ifdef USE_NET_ISLAND_CONNECT MemArena *arena; @@ -181,50 +237,43 @@ typedef struct KnifeTool_OpData { GHash *kedgefacemap; GHash *facetrimap; - BMBVHTree *bmbvh; + KnifeBVH bvh; + const float (**cagecos)[3]; BLI_mempool *kverts; BLI_mempool *kedges; + BLI_Stack *undostack; + BLI_Stack *splitstack; /* Store edge splits by #knife_split_edge. */ + float vthresh; float ethresh; - /* used for drag-cutting */ + /* Used for drag-cutting. */ KnifeLineHit *linehits; int totlinehit; - /* Data for mouse-position-derived data */ - KnifePosData curr; /* current point under the cursor */ - KnifePosData prev; /* last added cut (a line draws from the cursor to this) */ - KnifePosData init; /* the first point in the cut-list, used for closing the loop */ + /* Data for mouse-position-derived data. */ + KnifePosData curr; /* Current point under the cursor. */ + KnifePosData prev; /* Last added cut (a line draws from the cursor to this). */ + KnifePosData init; /* The first point in the cut-list, used for closing the loop. */ - /** Number of knife edges `kedges`. */ + /* Number of knife edges `kedges`. */ int totkedge; - /** Number of knife vertices, `kverts`. */ + /* Number of knife vertices, `kverts`. */ int totkvert; BLI_mempool *refs; - /** - * Use this instead of #Object.imat since it's calculated using #invert_m4_m4_safe_ortho - * to support objects with zero scale on a single axis. - */ - float ob_imat[4][4]; - - float projmat[4][4]; - float projmat_inv[4][4]; - /* vector along view z axis (object space, normalized) */ - float proj_zaxis[3]; - KnifeColors colors; - /* run by the UI or not */ + /* Run by the UI or not. */ bool is_interactive; /* Operator options. */ - bool cut_through; /* preference, can be modified at runtime (that feature may go) */ - bool only_select; /* set on initialization */ - bool select_result; /* set on initialization */ + bool cut_through; /* Preference, can be modified at runtime (that feature may go). */ + bool only_select; /* Set on initialization. */ + bool select_result; /* Set on initialization. */ bool is_ortho; float ortho_extent; @@ -240,17 +289,38 @@ typedef struct KnifeTool_OpData { bool ignore_edge_snapping; bool ignore_vert_snapping; - /* use to check if we're currently dragging an angle snapped line */ + NumInput num; + float angle_snapping_increment; /* Degrees */ + + /* Use to check if we're currently dragging an angle snapped line. */ + short angle_snapping_mode; bool is_angle_snapping; bool angle_snapping; float angle; + /* Relative angle snapping reference edge. */ + KnifeEdge *snap_ref_edge; + int snap_ref_edges_count; + int snap_edge; /* Used by #KNF_MODAL_CYCLE_ANGLE_SNAP_EDGE to choose an edge for snapping. */ + + short constrain_axis; + short constrain_axis_mode; + bool axis_constrained; + char axis_string[2]; + + short dist_angle_mode; + bool show_dist_angle; + KnifeMeasureData mdata; /* Data for distance and angle drawing calculations. */ - const float (*cagecos)[3]; + KnifeUndoFrame *undo; /* Current undo frame. */ + bool is_drag_undo; + + bool depth_test; } KnifeTool_OpData; enum { KNF_MODAL_CANCEL = 1, KNF_MODAL_CONFIRM, + KNF_MODAL_UNDO, KNF_MODAL_MIDPOINT_ON, KNF_MODAL_MIDPOINT_OFF, KNF_MODAL_NEW_CUT, @@ -258,45 +328,72 @@ enum { KNF_MODAL_IGNORE_SNAP_OFF, KNF_MODAL_ADD_CUT, KNF_MODAL_ANGLE_SNAP_TOGGLE, + KNF_MODAL_CYCLE_ANGLE_SNAP_EDGE, KNF_MODAL_CUT_THROUGH_TOGGLE, + KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE, + KNF_MODAL_DEPTH_TEST_TOGGLE, KNF_MODAL_PANNING, KNF_MODAL_ADD_CUT_CLOSED, }; +enum { + KNF_CONSTRAIN_ANGLE_MODE_NONE = 0, + KNF_CONSTRAIN_ANGLE_MODE_SCREEN = 1, + KNF_CONSTRAIN_ANGLE_MODE_RELATIVE = 2 +}; + +enum { + KNF_CONSTRAIN_AXIS_NONE = 0, + KNF_CONSTRAIN_AXIS_X = 1, + KNF_CONSTRAIN_AXIS_Y = 2, + KNF_CONSTRAIN_AXIS_Z = 3 +}; + +enum { + KNF_CONSTRAIN_AXIS_MODE_NONE = 0, + KNF_CONSTRAIN_AXIS_MODE_GLOBAL = 1, + KNF_CONSTRAIN_AXIS_MODE_LOCAL = 2 +}; + +enum { + KNF_MEASUREMENT_NONE = 0, + KNF_MEASUREMENT_BOTH = 1, + KNF_MEASUREMENT_DISTANCE = 2, + KNF_MEASUREMENT_ANGLE = 3 +}; + /* -------------------------------------------------------------------- */ /** \name Drawing * \{ */ -static void knifetool_draw_angle_snapping(const KnifeTool_OpData *kcd) +#if 1 +static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3], float r_v2[3]) { - float v1[3], v2[3]; float planes[4][4]; + planes_from_projmat( + kcd->vc.rv3d->persmat, planes[2], planes[0], planes[1], planes[3], NULL, NULL); - planes_from_projmat(kcd->projmat, planes[2], planes[0], planes[1], planes[3], NULL, NULL); - - /* ray-cast all planes */ + /* Ray-cast all planes. */ { float ray_dir[3]; float ray_hit_best[2][3] = {{UNPACK3(kcd->prev.cage)}, {UNPACK3(kcd->curr.cage)}}; float lambda_best[2] = {-FLT_MAX, FLT_MAX}; int i; - /* we (sometimes) need the lines to be at the same depth before projecting */ -#if 0 + /* We (sometimes) need the lines to be at the same depth before projecting. */ +# if 0 sub_v3_v3v3(ray_dir, kcd->curr.cage, kcd->prev.cage); -#else +# else { float curr_cage_adjust[3]; float co_depth[3]; copy_v3_v3(co_depth, kcd->prev.cage); - mul_m4_v3(kcd->ob->obmat, co_depth); ED_view3d_win_to_3d(kcd->vc.v3d, kcd->region, co_depth, kcd->curr.mval, curr_cage_adjust); - mul_m4_v3(kcd->ob_imat, curr_cage_adjust); sub_v3_v3v3(ray_dir, curr_cage_adjust, kcd->prev.cage); } -#endif +# endif for (i = 0; i < 4; i++) { float ray_hit[3]; @@ -318,9 +415,16 @@ static void knifetool_draw_angle_snapping(const KnifeTool_OpData *kcd) } } - copy_v3_v3(v1, ray_hit_best[0]); - copy_v3_v3(v2, ray_hit_best[1]); + copy_v3_v3(r_v1, ray_hit_best[0]); + copy_v3_v3(r_v2, ray_hit_best[1]); } +} + +static void knifetool_draw_angle_snapping(const KnifeTool_OpData *kcd) +{ + float v1[3], v2[3]; + + knifetool_raycast_planes(kcd, v1, v2); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); @@ -336,21 +440,461 @@ static void knifetool_draw_angle_snapping(const KnifeTool_OpData *kcd) immUnbindProgram(); } -/* modal loop selection drawing callback */ -static void knifetool_draw(const bContext *UNUSED(C), ARegion *UNUSED(region), void *arg) +static void knifetool_draw_orientation_locking(const KnifeTool_OpData *kcd) { - const KnifeTool_OpData *kcd = arg; - GPU_depth_test(GPU_DEPTH_NONE); + if (!compare_v3v3(kcd->prev.cage, kcd->curr.cage, KNIFE_FLT_EPSBIG)) { + float v1[3], v2[3]; + + /* This is causing buggyness when prev.cage and curr.cage are too close together. */ + knifetool_raycast_planes(kcd, v1, v2); + + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + switch (kcd->constrain_axis) { + case KNF_CONSTRAIN_AXIS_X: { + immUniformColor3ubv(kcd->colors.xaxis); + break; + } + case KNF_CONSTRAIN_AXIS_Y: { + immUniformColor3ubv(kcd->colors.yaxis); + break; + } + case KNF_CONSTRAIN_AXIS_Z: { + immUniformColor3ubv(kcd->colors.zaxis); + break; + } + default: { + immUniformColor3ubv(kcd->colors.axis_extra); + break; + } + } + + GPU_line_width(2.0); + + immBegin(GPU_PRIM_LINES, 2); + immVertex3fv(pos, v1); + immVertex3fv(pos, v2); + immEnd(); + + immUnbindProgram(); + } +} +#endif +static void knifetool_draw_visible_distances(const KnifeTool_OpData *kcd) +{ GPU_matrix_push_projection(); - GPU_polygon_offset(1.0f, 1.0f); + GPU_matrix_push(); + GPU_matrix_identity_set(); + wmOrtho2_region_pixelspace(kcd->region); + + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + char numstr[256]; + float numstr_size[2]; + float posit[2]; + const float bg_margin = 4.0f * U.dpi_fac; + const int font_size = 14.0f * U.pixelsize; + const int distance_precision = 4; + + /* Calculate distance and convert to string. */ + const float cut_len = len_v3v3(kcd->mdata.corr_prev_cage, kcd->curr.cage); + + UnitSettings *unit = &kcd->scene->unit; + if (unit->system == USER_UNIT_NONE) { + BLI_snprintf(numstr, sizeof(numstr), "%.*f", distance_precision, cut_len); + } + else { + BKE_unit_value_as_string(numstr, + sizeof(numstr), + (double)(cut_len * unit->scale_length), + distance_precision, + B_UNIT_LENGTH, + unit, + false); + } + + BLF_enable(blf_mono_font, BLF_ROTATION); + BLF_size(blf_mono_font, font_size, U.dpi); + BLF_rotation(blf_mono_font, 0.0f); + BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]); + + /* Center text. */ + mid_v2_v2v2(posit, kcd->prev.mval, kcd->curr.mval); + posit[0] -= numstr_size[0] / 2.0f; + posit[1] -= numstr_size[1] / 2.0f; + + /* Draw text background. */ + float color_back[4] = {0.0f, 0.0f, 0.0f, 0.5f}; /* TODO: Replace with theme color. */ + immUniformColor4fv(color_back); + + GPU_blend(GPU_BLEND_ALPHA); + immRectf(pos, + posit[0] - bg_margin, + posit[1] - bg_margin, + posit[0] + bg_margin + numstr_size[0], + posit[1] + bg_margin + numstr_size[1]); + GPU_blend(GPU_BLEND_NONE); + immUnbindProgram(); + + /* Draw text. */ + uchar color_text[3]; + UI_GetThemeColor3ubv(TH_TEXT, color_text); + + BLF_color3ubv(blf_mono_font, color_text); + BLF_position(blf_mono_font, posit[0], posit[1], 0.0f); + BLF_draw(blf_mono_font, numstr, sizeof(numstr)); + BLF_disable(blf_mono_font, BLF_ROTATION); + + GPU_matrix_pop(); + GPU_matrix_pop_projection(); +} + +static void knifetool_draw_angle(const KnifeTool_OpData *kcd, + const float start[3], + const float mid[3], + const float end[3], + const float start_ss[2], + const float mid_ss[2], + const float end_ss[2], + const float angle) +{ + const RegionView3D *rv3d = kcd->region->regiondata; + const int arc_steps = 24; + const float arc_size = 64.0f * U.dpi_fac; + const float bg_margin = 4.0f * U.dpi_fac; + const float cap_size = 4.0f * U.dpi_fac; + const int font_size = 14 * U.pixelsize; + const int angle_precision = 3; + + /* Angle arc in 3d space. */ + GPU_blend(GPU_BLEND_ALPHA); + + const uint pos_3d = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + { + float dir_tmp[3]; + float ar_coord[3]; + + float dir_a[3]; + float dir_b[3]; + float quat[4]; + float axis[3]; + float arc_angle; + + const float inverse_average_scale = 1 / + (kcd->curr.ob->obmat[0][0] + kcd->curr.ob->obmat[1][1] + + kcd->curr.ob->obmat[2][2]); + const float px_scale = + 3.0f * inverse_average_scale * + (ED_view3d_pixel_size_no_ui_scale(rv3d, mid) * + min_fff(arc_size, len_v2v2(start_ss, mid_ss) / 2.0f, len_v2v2(end_ss, mid_ss) / 2.0f)); + + sub_v3_v3v3(dir_a, start, mid); + sub_v3_v3v3(dir_b, end, mid); + normalize_v3(dir_a); + normalize_v3(dir_b); + + cross_v3_v3v3(axis, dir_a, dir_b); + arc_angle = angle_normalized_v3v3(dir_a, dir_b); + + axis_angle_to_quat(quat, axis, arc_angle / arc_steps); + + copy_v3_v3(dir_tmp, dir_a); + + immUniformThemeColor3(TH_WIRE); + GPU_line_width(1.0); + + immBegin(GPU_PRIM_LINE_STRIP, arc_steps + 1); + for (int j = 0; j <= arc_steps; j++) { + madd_v3_v3v3fl(ar_coord, mid, dir_tmp, px_scale); + mul_qt_v3(quat, dir_tmp); + + immVertex3fv(pos_3d, ar_coord); + } + immEnd(); + } + + immUnbindProgram(); + + /* Angle text and background in 2d space. */ + GPU_matrix_push_projection(); GPU_matrix_push(); - GPU_matrix_mul(kcd->ob->obmat); + GPU_matrix_identity_set(); + wmOrtho2_region_pixelspace(kcd->region); + + uint pos_2d = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + /* Angle as string. */ + char numstr[256]; + float numstr_size[2]; + float posit[2]; + + UnitSettings *unit = &kcd->scene->unit; + if (unit->system == USER_UNIT_NONE) { + BLI_snprintf(numstr, sizeof(numstr), "%.*f°", angle_precision, RAD2DEGF(angle)); + } + else { + BKE_unit_value_as_string( + numstr, sizeof(numstr), (double)angle, angle_precision, B_UNIT_ROTATION, unit, false); + } + + BLF_enable(blf_mono_font, BLF_ROTATION); + BLF_size(blf_mono_font, font_size, U.dpi); + BLF_rotation(blf_mono_font, 0.0f); + BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]); + + posit[0] = mid_ss[0] + (cap_size * 2.0f); + posit[1] = mid_ss[1] - (numstr_size[1] / 2.0f); - if (kcd->mode == MODE_DRAGGING && kcd->is_angle_snapping) { - knifetool_draw_angle_snapping(kcd); + /* Draw text background. */ + float color_back[4] = {0.0f, 0.0f, 0.0f, 0.5f}; /* TODO: Replace with theme color. */ + immUniformColor4fv(color_back); + + GPU_blend(GPU_BLEND_ALPHA); + immRectf(pos_2d, + posit[0] - bg_margin, + posit[1] - bg_margin, + posit[0] + bg_margin + numstr_size[0], + posit[1] + bg_margin + numstr_size[1]); + GPU_blend(GPU_BLEND_NONE); + immUnbindProgram(); + + /* Draw text. */ + uchar color_text[3]; + UI_GetThemeColor3ubv(TH_TEXT, color_text); + + BLF_color3ubv(blf_mono_font, color_text); + BLF_position(blf_mono_font, posit[0], posit[1], 0.0f); + BLF_rotation(blf_mono_font, 0.0f); + BLF_draw(blf_mono_font, numstr, sizeof(numstr)); + BLF_disable(blf_mono_font, BLF_ROTATION); + + GPU_matrix_pop(); + GPU_matrix_pop_projection(); + + GPU_blend(GPU_BLEND_NONE); +} + +static void knifetool_draw_visible_angles(const KnifeTool_OpData *kcd) +{ + Ref *ref; + KnifeVert *kfv; + KnifeVert *tempkfv; + KnifeEdge *kfe; + KnifeEdge *tempkfe; + + if (kcd->curr.vert) { + kfv = kcd->curr.vert; + + float min_angle = FLT_MAX; + float angle = 0.0f; + float *end; + + kfe = ((Ref *)kfv->edges.first)->ref; + for (ref = kfv->edges.first; ref; ref = ref->next) { + tempkfe = ref->ref; + if (tempkfe->v1 != kfv) { + tempkfv = tempkfe->v1; + } + else { + tempkfv = tempkfe->v2; + } + angle = angle_v3v3v3(kcd->mdata.corr_prev_cage, kcd->curr.cage, tempkfv->cageco); + if (angle < min_angle) { + min_angle = angle; + kfe = tempkfe; + end = tempkfv->cageco; + } + } + + if (min_angle > KNIFE_FLT_EPSBIG) { + /* Last vertex in screen space. */ + float end_ss[2]; + ED_view3d_project_float_global(kcd->region, end, end_ss, V3D_PROJ_TEST_NOP); + + knifetool_draw_angle(kcd, + kcd->mdata.corr_prev_cage, + kcd->curr.cage, + end, + kcd->prev.mval, + kcd->curr.mval, + end_ss, + min_angle); + } + } + else if (kcd->curr.edge) { + kfe = kcd->curr.edge; + + /* Check for most recent cut (if cage is part of previous cut). */ + if (!compare_v3v3(kfe->v1->cageco, kcd->mdata.corr_prev_cage, KNIFE_FLT_EPSBIG) && + !compare_v3v3(kfe->v2->cageco, kcd->mdata.corr_prev_cage, KNIFE_FLT_EPSBIG)) { + /* Determine acute angle. */ + float angle1 = angle_v3v3v3(kcd->mdata.corr_prev_cage, kcd->curr.cage, kfe->v1->cageco); + float angle2 = angle_v3v3v3(kcd->mdata.corr_prev_cage, kcd->curr.cage, kfe->v2->cageco); + + float angle; + float *end; + if (angle1 < angle2) { + angle = angle1; + end = kfe->v1->cageco; + } + else { + angle = angle2; + end = kfe->v2->cageco; + } + + /* Last vertex in screen space. */ + float end_ss[2]; + ED_view3d_project_float_global(kcd->region, end, end_ss, V3D_PROJ_TEST_NOP); + + knifetool_draw_angle(kcd, + kcd->mdata.corr_prev_cage, + kcd->curr.cage, + end, + kcd->prev.mval, + kcd->curr.mval, + end_ss, + angle); + } + } + + if (kcd->prev.vert) { + kfv = kcd->prev.vert; + float min_angle = FLT_MAX; + float angle = 0.0f; + float *end; + + /* If using relative angle snapping, always draw angle to reference edge. */ + if (kcd->is_angle_snapping && kcd->angle_snapping_mode == KNF_CONSTRAIN_ANGLE_MODE_RELATIVE) { + kfe = kcd->snap_ref_edge; + if (kfe->v1 != kfv) { + tempkfv = kfe->v1; + } + else { + tempkfv = kfe->v2; + } + min_angle = angle_v3v3v3(kcd->curr.cage, kcd->prev.cage, tempkfv->cageco); + end = tempkfv->cageco; + } + else { + /* Choose minimum angle edge. */ + kfe = ((Ref *)kfv->edges.first)->ref; + for (ref = kfv->edges.first; ref; ref = ref->next) { + tempkfe = ref->ref; + if (tempkfe->v1 != kfv) { + tempkfv = tempkfe->v1; + } + else { + tempkfv = tempkfe->v2; + } + angle = angle_v3v3v3(kcd->curr.cage, kcd->prev.cage, tempkfv->cageco); + if (angle < min_angle) { + min_angle = angle; + kfe = tempkfe; + end = tempkfv->cageco; + } + } + } + + if (min_angle > KNIFE_FLT_EPSBIG) { + /* Last vertex in screen space. */ + float end_ss[2]; + ED_view3d_project_float_global(kcd->region, end, end_ss, V3D_PROJ_TEST_NOP); + + knifetool_draw_angle(kcd, + kcd->curr.cage, + kcd->prev.cage, + end, + kcd->curr.mval, + kcd->prev.mval, + end_ss, + min_angle); + } + } + else if (kcd->prev.edge) { + /* Determine acute angle. */ + kfe = kcd->prev.edge; + float angle1 = angle_v3v3v3(kcd->curr.cage, kcd->prev.cage, kfe->v1->cageco); + float angle2 = angle_v3v3v3(kcd->curr.cage, kcd->prev.cage, kfe->v2->cageco); + + float angle; + float *end; + /* kcd->prev.edge can have one vertex part of cut and one part of mesh? */ + /* This never seems to happen for kcd->curr.edge. */ + if ((!kcd->prev.vert || kcd->prev.vert->v == kfe->v1->v) || kfe->v1->is_cut) { + angle = angle2; + end = kfe->v2->cageco; + } + else if ((!kcd->prev.vert || kcd->prev.vert->v == kfe->v2->v) || kfe->v2->is_cut) { + angle = angle1; + end = kfe->v1->cageco; + } + else { + if (angle1 < angle2) { + angle = angle1; + end = kfe->v1->cageco; + } + else { + angle = angle2; + end = kfe->v2->cageco; + } + } + + /* Last vertex in screen space. */ + float end_ss[2]; + ED_view3d_project_float_global(kcd->region, end, end_ss, V3D_PROJ_TEST_NOP); + + knifetool_draw_angle( + kcd, kcd->curr.cage, kcd->prev.cage, end, kcd->curr.mval, kcd->prev.mval, end_ss, angle); + } + else if (kcd->mdata.is_stored && !kcd->prev.is_space) { + float angle = angle_v3v3v3(kcd->curr.cage, kcd->mdata.corr_prev_cage, kcd->mdata.cage); + knifetool_draw_angle(kcd, + kcd->curr.cage, + kcd->mdata.corr_prev_cage, + kcd->mdata.cage, + kcd->curr.mval, + kcd->prev.mval, + kcd->mdata.mval, + angle); + } +} + +static void knifetool_draw_dist_angle(const KnifeTool_OpData *kcd) +{ + switch (kcd->dist_angle_mode) { + case KNF_MEASUREMENT_BOTH: { + knifetool_draw_visible_distances(kcd); + knifetool_draw_visible_angles(kcd); + break; + } + case KNF_MEASUREMENT_DISTANCE: { + knifetool_draw_visible_distances(kcd); + break; + } + case KNF_MEASUREMENT_ANGLE: { + knifetool_draw_visible_angles(kcd); + break; + } } +} + +/* Modal loop selection drawing callback. */ +static void knifetool_draw(const bContext *UNUSED(C), ARegion *UNUSED(region), void *arg) +{ + const KnifeTool_OpData *kcd = arg; + GPU_depth_test(GPU_DEPTH_NONE); + + GPU_matrix_push_projection(); + GPU_polygon_offset(1.0f, 1.0f); GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); @@ -412,48 +956,8 @@ static void knifetool_draw(const bContext *UNUSED(C), ARegion *UNUSED(region), v immEnd(); } - if (kcd->totlinehit > 0) { - KnifeLineHit *lh; - int i, snapped_verts_count, other_verts_count; - float fcol[4]; - - GPU_blend(GPU_BLEND_ALPHA); - - GPUVertBuf *vert = GPU_vertbuf_create_with_format(format); - GPU_vertbuf_data_alloc(vert, kcd->totlinehit); - - lh = kcd->linehits; - for (i = 0, snapped_verts_count = 0, other_verts_count = 0; i < kcd->totlinehit; i++, lh++) { - if (lh->v) { - GPU_vertbuf_attr_set(vert, pos, snapped_verts_count++, lh->cagehit); - } - else { - GPU_vertbuf_attr_set(vert, pos, kcd->totlinehit - 1 - other_verts_count++, lh->cagehit); - } - } - - GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_POINTS, vert, NULL, GPU_BATCH_OWNS_VBO); - GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_UNIFORM_COLOR); - - /* draw any snapped verts first */ - rgba_uchar_to_float(fcol, kcd->colors.point_a); - GPU_batch_uniform_4fv(batch, "color", fcol); - GPU_point_size(11 * UI_DPI_FAC); - if (snapped_verts_count > 0) { - GPU_batch_draw_range(batch, 0, snapped_verts_count); - } - - /* now draw the rest */ - rgba_uchar_to_float(fcol, kcd->colors.curpoint_a); - GPU_batch_uniform_4fv(batch, "color", fcol); - GPU_point_size(7 * UI_DPI_FAC); - if (other_verts_count > 0) { - GPU_batch_draw_range(batch, snapped_verts_count, other_verts_count); - } - - GPU_batch_discard(batch); - - GPU_blend(GPU_BLEND_NONE); + if (kcd->depth_test) { + GPU_depth_test(GPU_DEPTH_LESS_EQUAL); } if (kcd->totkedge > 0) { @@ -467,7 +971,7 @@ static void knifetool_draw(const bContext *UNUSED(C), ARegion *UNUSED(region), v BLI_mempool_iternew(kcd->kedges, &iter); for (kfe = BLI_mempool_iterstep(&iter); kfe; kfe = BLI_mempool_iterstep(&iter)) { - if (!kfe->is_cut) { + if (!kfe->is_cut || kfe->is_invalid) { continue; } @@ -492,7 +996,7 @@ static void knifetool_draw(const bContext *UNUSED(C), ARegion *UNUSED(region), v BLI_mempool_iternew(kcd->kverts, &iter); for (kfv = BLI_mempool_iterstep(&iter); kfv; kfv = BLI_mempool_iterstep(&iter)) { - if (!kfv->is_cut) { + if (!kfv->is_cut || kfv->is_invalid) { continue; } @@ -505,12 +1009,81 @@ static void knifetool_draw(const bContext *UNUSED(C), ARegion *UNUSED(region), v GPU_batch_discard(batch); } + /* Draw relative angle snapping reference edge. */ + if (kcd->is_angle_snapping && kcd->angle_snapping_mode == KNF_CONSTRAIN_ANGLE_MODE_RELATIVE) { + immUniformColor3ubv(kcd->colors.edge_extra); + GPU_line_width(2.0); + + immBegin(GPU_PRIM_LINES, 2); + immVertex3fv(pos, kcd->snap_ref_edge->v1->cageco); + immVertex3fv(pos, kcd->snap_ref_edge->v2->cageco); + immEnd(); + } + + if (kcd->totlinehit > 0) { + KnifeLineHit *lh; + int i, snapped_verts_count, other_verts_count; + float fcol[4]; + + GPU_blend(GPU_BLEND_ALPHA); + + GPUVertBuf *vert = GPU_vertbuf_create_with_format(format); + GPU_vertbuf_data_alloc(vert, kcd->totlinehit); + + lh = kcd->linehits; + for (i = 0, snapped_verts_count = 0, other_verts_count = 0; i < kcd->totlinehit; i++, lh++) { + if (lh->v) { + GPU_vertbuf_attr_set(vert, pos, snapped_verts_count++, lh->cagehit); + } + else { + GPU_vertbuf_attr_set(vert, pos, kcd->totlinehit - 1 - other_verts_count++, lh->cagehit); + } + } + + GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_POINTS, vert, NULL, GPU_BATCH_OWNS_VBO); + GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_UNIFORM_COLOR); + + /* Draw any snapped verts first. */ + rgba_uchar_to_float(fcol, kcd->colors.point_a); + GPU_batch_uniform_4fv(batch, "color", fcol); + GPU_point_size(11 * UI_DPI_FAC); + if (snapped_verts_count > 0) { + GPU_batch_draw_range(batch, 0, snapped_verts_count); + } + + /* Now draw the rest. */ + rgba_uchar_to_float(fcol, kcd->colors.curpoint_a); + GPU_batch_uniform_4fv(batch, "color", fcol); + GPU_point_size(7 * UI_DPI_FAC); + if (other_verts_count > 0) { + GPU_batch_draw_range(batch, snapped_verts_count, other_verts_count); + } + + GPU_batch_discard(batch); + + GPU_blend(GPU_BLEND_NONE); + } + immUnbindProgram(); - GPU_matrix_pop(); + GPU_depth_test(GPU_DEPTH_NONE); + + if (kcd->mode == MODE_DRAGGING) { + if (kcd->is_angle_snapping) { + knifetool_draw_angle_snapping(kcd); + } + else if (kcd->axis_constrained) { + knifetool_draw_orientation_locking(kcd); + } + + if (kcd->show_dist_angle) { + knifetool_draw_dist_angle(kcd); + } + } + GPU_matrix_pop_projection(); - /* Reset default */ + /* Reset default. */ GPU_depth_test(GPU_DEPTH_LESS_EQUAL); } @@ -532,27 +1105,50 @@ static void knife_update_header(bContext *C, wmOperator *op, KnifeTool_OpData *k WM_modalkeymap_operator_items_to_string_buf( \ op->type, (_id), true, UI_MAX_SHORTCUT_STR, &available_len, &p) - BLI_snprintf(header, - sizeof(header), - TIP_("%s: confirm, %s: cancel, " - "%s: start/define cut, %s: close cut, %s: new cut, " - "%s: midpoint snap (%s), %s: ignore snap (%s), " - "%s: angle constraint (%s), %s: cut through (%s), " - "%s: panning"), - WM_MODALKEY(KNF_MODAL_CONFIRM), - WM_MODALKEY(KNF_MODAL_CANCEL), - WM_MODALKEY(KNF_MODAL_ADD_CUT), - WM_MODALKEY(KNF_MODAL_ADD_CUT_CLOSED), - WM_MODALKEY(KNF_MODAL_NEW_CUT), - WM_MODALKEY(KNF_MODAL_MIDPOINT_ON), - WM_bool_as_string(kcd->snap_midpoints), - WM_MODALKEY(KNF_MODAL_IGNORE_SNAP_ON), - WM_bool_as_string(kcd->ignore_edge_snapping), - WM_MODALKEY(KNF_MODAL_ANGLE_SNAP_TOGGLE), - WM_bool_as_string(kcd->angle_snapping), - WM_MODALKEY(KNF_MODAL_CUT_THROUGH_TOGGLE), - WM_bool_as_string(kcd->cut_through), - WM_MODALKEY(KNF_MODAL_PANNING)); + BLI_snprintf( + header, + sizeof(header), + TIP_("%s: confirm, %s: cancel, %s: undo, " + "%s: start/define cut, %s: close cut, %s: new cut, " + "%s: midpoint snap (%s), %s: ignore snap (%s), " + "%s: angle constraint %.2f(%.2f) (%s%s%s%s), %s: cut through (%s), " + "%s: panning, XYZ: orientation lock (%s), " + "%s: distance/angle measurements (%s), " + "%s: x-ray (%s)"), + WM_MODALKEY(KNF_MODAL_CONFIRM), + WM_MODALKEY(KNF_MODAL_CANCEL), + WM_MODALKEY(KNF_MODAL_UNDO), + WM_MODALKEY(KNF_MODAL_ADD_CUT), + WM_MODALKEY(KNF_MODAL_ADD_CUT_CLOSED), + WM_MODALKEY(KNF_MODAL_NEW_CUT), + WM_MODALKEY(KNF_MODAL_MIDPOINT_ON), + WM_bool_as_string(kcd->snap_midpoints), + WM_MODALKEY(KNF_MODAL_IGNORE_SNAP_ON), + WM_bool_as_string(kcd->ignore_edge_snapping), + WM_MODALKEY(KNF_MODAL_ANGLE_SNAP_TOGGLE), + (kcd->angle >= 0.0f) ? RAD2DEGF(kcd->angle) : 360.0f + RAD2DEGF(kcd->angle), + (kcd->angle_snapping_increment > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT && + kcd->angle_snapping_increment < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) ? + kcd->angle_snapping_increment : + KNIFE_DEFAULT_ANGLE_SNAPPING_INCREMENT, + kcd->angle_snapping ? + ((kcd->angle_snapping_mode == KNF_CONSTRAIN_ANGLE_MODE_SCREEN) ? "Screen" : "Relative") : + "OFF", + /* TODO: Can this be simplified? */ + (kcd->angle_snapping_mode == KNF_CONSTRAIN_ANGLE_MODE_RELATIVE) ? " - " : "", + (kcd->angle_snapping_mode == KNF_CONSTRAIN_ANGLE_MODE_RELATIVE) ? + WM_MODALKEY(KNF_MODAL_CYCLE_ANGLE_SNAP_EDGE) : + "", + (kcd->angle_snapping_mode == KNF_CONSTRAIN_ANGLE_MODE_RELATIVE) ? ": cycle edge" : "", + /**/ + WM_MODALKEY(KNF_MODAL_CUT_THROUGH_TOGGLE), + WM_bool_as_string(kcd->cut_through), + WM_MODALKEY(KNF_MODAL_PANNING), + (kcd->axis_constrained ? kcd->axis_string : WM_bool_as_string(kcd->axis_constrained)), + WM_MODALKEY(KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE), + WM_bool_as_string(kcd->show_dist_angle), + WM_MODALKEY(KNF_MODAL_DEPTH_TEST_TOGGLE), + WM_bool_as_string(!kcd->depth_test)); #undef WM_MODALKEY @@ -562,27 +1158,330 @@ static void knife_update_header(bContext *C, wmOperator *op, KnifeTool_OpData *k /** \} */ /* -------------------------------------------------------------------- */ +/** \name Knife BVH Utils + * \{ */ + +static bool knife_bm_face_is_select(BMFace *f) +{ + return (BM_elem_flag_test(f, BM_ELEM_SELECT) != 0); +} + +static bool knife_bm_face_is_not_hidden(BMFace *f) +{ + return (BM_elem_flag_test(f, BM_ELEM_HIDDEN) == 0); +} + +static void knife_bvh_init(KnifeTool_OpData *kcd) +{ + Object *ob; + BMEditMesh *em; + + /* Test Function. */ + bool (*test_fn)(BMFace *); + if ((kcd->only_select && kcd->cut_through)) { + test_fn = knife_bm_face_is_select; + } + else { + test_fn = knife_bm_face_is_not_hidden; + } + + /* Construct BVH Tree. */ + float cos[3][3]; + const float epsilon = FLT_EPSILON * 2.0f; + int tottri = 0; + int ob_tottri = 0; + BMLoop *(*looptris)[3]; + BMFace *f_test = NULL, *f_test_prev = NULL; + bool test_fn_ret = false; + + /* Calculate tottri. */ + for (uint b = 0; b < kcd->objects_len; b++) { + ob_tottri = 0; + ob = kcd->objects[b]; + em = BKE_editmesh_from_object(ob); + + for (int i = 0; i < em->tottri; i++) { + f_test = em->looptris[i][0]->f; + if (f_test != f_test_prev) { + test_fn_ret = test_fn(f_test); + f_test_prev = f_test; + } + + if (test_fn_ret) { + ob_tottri++; + } + } + + tottri += ob_tottri; + } + + kcd->bvh.tree = BLI_bvhtree_new(tottri, epsilon, 8, 8); + + f_test_prev = NULL; + test_fn_ret = false; + + /* Add tri's for each object. + * TODO: + * test_fn can leave large gaps between bvh tree indices. + * Compacting bvh tree indices may be possible. + * Don't forget to update #knife_bvh_intersect_plane! + */ + tottri = 0; + for (uint b = 0; b < kcd->objects_len; b++) { + ob = kcd->objects[b]; + em = BKE_editmesh_from_object(ob); + looptris = em->looptris; + + for (int i = 0; i < em->tottri; i++) { + + f_test = looptris[i][0]->f; + if (f_test != f_test_prev) { + test_fn_ret = test_fn(f_test); + f_test_prev = f_test; + } + + if (!test_fn_ret) { + continue; + } + + copy_v3_v3(cos[0], kcd->cagecos[b][BM_elem_index_get(looptris[i][0]->v)]); + copy_v3_v3(cos[1], kcd->cagecos[b][BM_elem_index_get(looptris[i][1]->v)]); + copy_v3_v3(cos[2], kcd->cagecos[b][BM_elem_index_get(looptris[i][2]->v)]); + + /* Convert to world-space. */ + mul_m4_v3(ob->obmat, cos[0]); + mul_m4_v3(ob->obmat, cos[1]); + mul_m4_v3(ob->obmat, cos[2]); + + BLI_bvhtree_insert(kcd->bvh.tree, i + tottri, (float *)cos, 3); + } + + tottri += em->tottri; + } + + BLI_bvhtree_balance(kcd->bvh.tree); +} + +/* Wrapper for #BLI_bvhtree_free. */ +static void knife_bvh_free(KnifeTool_OpData *kcd) +{ + if (kcd->bvh.tree) { + BLI_bvhtree_free(kcd->bvh.tree); + kcd->bvh.tree = NULL; + } +} + +static void knife_bvh_raycast_cb(void *userdata, + int index, + const BVHTreeRay *ray, + BVHTreeRayHit *hit) +{ + KnifeTool_OpData *kcd = userdata; + BMLoop **ltri; + Object *ob; + BMEditMesh *em; + + float dist, uv[2]; + bool isect; + int tottri; + float tri_cos[3][3]; + + if (index != -1) { + tottri = 0; + uint b = 0; + for (; b < kcd->objects_len; b++) { + index -= tottri; + ob = kcd->objects[b]; + em = BKE_editmesh_from_object(ob); + tottri = em->tottri; + if (index < tottri) { + ltri = em->looptris[index]; + break; + } + } + + if (kcd->bvh.filter_cb) { + if (!kcd->bvh.filter_cb(ltri[0]->f, kcd->bvh.filter_data)) { + return; + } + } + + copy_v3_v3(tri_cos[0], kcd->cagecos[b][BM_elem_index_get(ltri[0]->v)]); + copy_v3_v3(tri_cos[1], kcd->cagecos[b][BM_elem_index_get(ltri[1]->v)]); + copy_v3_v3(tri_cos[2], kcd->cagecos[b][BM_elem_index_get(ltri[2]->v)]); + mul_m4_v3(ob->obmat, tri_cos[0]); + mul_m4_v3(ob->obmat, tri_cos[1]); + mul_m4_v3(ob->obmat, tri_cos[2]); + + isect = + (ray->radius > 0.0f ? + isect_ray_tri_epsilon_v3(ray->origin, + ray->direction, + tri_cos[0], + tri_cos[1], + tri_cos[2], + &dist, + uv, + ray->radius) : +#ifdef USE_KDOPBVH_WATERTIGHT + isect_ray_tri_watertight_v3( + ray->origin, ray->isect_precalc, tri_cos[0], tri_cos[1], tri_cos[2], &dist, uv)); +#else + isect_ray_tri_v3( + ray->origin, ray->direction, tri_cos[0], tri_cos[1], tri_cos[2], &dist, uv)); +#endif + + if (isect && dist < hit->dist) { + hit->dist = dist; + hit->index = index; + + copy_v3_v3(hit->no, ltri[0]->f->no); + + madd_v3_v3v3fl(hit->co, ray->origin, ray->direction, dist); + + kcd->bvh.looptris = em->looptris; + copy_v2_v2(kcd->bvh.uv, uv); + kcd->bvh.base_index = b; + } + } +} + +/* `co` is expected to be in world space. */ +static BMFace *knife_bvh_raycast(KnifeTool_OpData *kcd, + const float co[3], + const float dir[3], + const float radius, + float *r_dist, + float r_hitout[3], + float r_cagehit[3], + uint *r_base_index) +{ + BMFace *face; + BMLoop **ltri; + BVHTreeRayHit hit; + const float dist = r_dist ? *r_dist : FLT_MAX; + hit.dist = dist; + hit.index = -1; + + BLI_bvhtree_ray_cast(kcd->bvh.tree, co, dir, radius, &hit, knife_bvh_raycast_cb, kcd); + + /* Handle Hit */ + if (hit.index != -1 && hit.dist != dist) { + face = kcd->bvh.looptris[hit.index][0]->f; + + /* Hits returned in world space. */ + if (r_hitout) { + ltri = kcd->bvh.looptris[hit.index]; + interp_v3_v3v3v3_uv(r_hitout, ltri[0]->v->co, ltri[1]->v->co, ltri[2]->v->co, kcd->bvh.uv); + + if (r_cagehit) { + copy_v3_v3(r_cagehit, hit.co); + } + } + + if (r_dist) { + *r_dist = hit.dist; + } + + if (r_base_index) { + *r_base_index = kcd->bvh.base_index; + } + + return face; + } + return NULL; +} + +/* `co` is expected to be in world space. */ +static BMFace *knife_bvh_raycast_filter(KnifeTool_OpData *kcd, + const float co[3], + const float dir[3], + const float radius, + float *r_dist, + float r_hitout[3], + float r_cagehit[3], + uint *r_base_index, + bool (*filter_cb)(BMFace *f, void *userdata), + void *filter_userdata) +{ + kcd->bvh.filter_cb = filter_cb; + kcd->bvh.filter_data = filter_userdata; + + BMFace *face; + BMLoop **ltri; + BVHTreeRayHit hit; + const float dist = r_dist ? *r_dist : FLT_MAX; + hit.dist = dist; + hit.index = -1; + + BLI_bvhtree_ray_cast(kcd->bvh.tree, co, dir, radius, &hit, knife_bvh_raycast_cb, kcd); + + kcd->bvh.filter_cb = NULL; + kcd->bvh.filter_data = NULL; + + /* Handle Hit */ + if (hit.index != -1 && hit.dist != dist) { + face = kcd->bvh.looptris[hit.index][0]->f; + + /* Hits returned in world space. */ + if (r_hitout) { + ltri = kcd->bvh.looptris[hit.index]; + interp_v3_v3v3v3_uv(r_hitout, ltri[0]->v->co, ltri[1]->v->co, ltri[2]->v->co, kcd->bvh.uv); + + if (r_cagehit) { + copy_v3_v3(r_cagehit, hit.co); + } + } + + if (r_dist) { + *r_dist = hit.dist; + } + + if (r_base_index) { + *r_base_index = kcd->bvh.base_index; + } + + return face; + } + return NULL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Geometry Utils * \{ */ static void knife_project_v2(const KnifeTool_OpData *kcd, const float co[3], float sco[2]) { - ED_view3d_project_float_v2_m4(kcd->region, co, sco, (float(*)[4])kcd->projmat); + ED_view3d_project_float_global(kcd->region, co, sco, V3D_PROJ_TEST_NOP); } +/* Ray is returned in world space. */ static void knife_input_ray_segment(KnifeTool_OpData *kcd, const float mval[2], const float ofs, float r_origin[3], float r_origin_ofs[3]) { - /* unproject to find view ray */ + /* Unproject to find view ray. */ ED_view3d_unproject_v3(kcd->vc.region, mval[0], mval[1], 0.0f, r_origin); ED_view3d_unproject_v3(kcd->vc.region, mval[0], mval[1], ofs, r_origin_ofs); +} + +static void knifetool_recast_cageco(KnifeTool_OpData *kcd, float mval[3], float r_cage[3]) +{ + float origin[3]; + float origin_ofs[3]; + float ray[3], ray_normal[3]; + float co[3]; /* Unused. */ - /* transform into object space */ - mul_m4_v3(kcd->ob_imat, r_origin); - mul_m4_v3(kcd->ob_imat, r_origin_ofs); + knife_input_ray_segment(kcd, mval, 1.0f, origin, origin_ofs); + + sub_v3_v3v3(ray, origin_ofs, origin); + normalize_v3_v3(ray_normal, ray); + + knife_bvh_raycast(kcd, origin, ray_normal, 0.0f, NULL, co, r_cage, NULL); } static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f) @@ -599,11 +1498,11 @@ static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f) l2 = v2->v ? BM_face_vert_share_loop(f, v2->v) : NULL; if ((l1 && l2) && BM_loop_is_adjacent(l1, l2)) { - /* boundary-case, always false to avoid edge-in-face checks below */ + /* Boundary-case, always false to avoid edge-in-face checks below. */ return false; } - /* find out if v1 and v2, if set, are part of the face */ + /* Find out if v1 and v2, if set, are part of the face. */ v1_inface = (l1 != NULL); v2_inface = (l2 != NULL); @@ -619,22 +1518,15 @@ static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f) /* Can have case where v1 and v2 are on shared chain between two faces. * BM_face_splits_check_legal does visibility and self-intersection tests, * but it is expensive and maybe a bit buggy, so use a simple - * "is the midpoint in the face" test */ + * "is the midpoint in the face" test. */ mid_v3_v3v3(mid, v1->co, v2->co); return BM_face_point_inside_test(f, mid); } return false; } -static void knife_recalc_projmat(KnifeTool_OpData *kcd) +static void knife_recalc_ortho(KnifeTool_OpData *kcd) { - ED_view3d_ob_project_mat_get(kcd->region->regiondata, kcd->ob, kcd->projmat); - invert_m4_m4(kcd->projmat_inv, kcd->projmat); - - invert_m4_m4_safe_ortho(kcd->ob_imat, kcd->ob->obmat); - mul_v3_mat3_m4v3(kcd->proj_zaxis, kcd->ob_imat, kcd->vc.rv3d->viewinv[2]); - normalize_v3(kcd->proj_zaxis); - kcd->is_ortho = ED_view3d_clip_range_get( kcd->vc.depsgraph, kcd->vc.v3d, kcd->vc.rv3d, &kcd->clipsta, &kcd->clipend, true); } @@ -762,7 +1654,7 @@ static void knife_add_edge_faces_to_vert(KnifeTool_OpData *kcd, KnifeVert *kfv, } /* Find a face in common in the two faces lists. - * If more than one, return the first; if none, return NULL */ + * If more than one, return the first; if none, return NULL. */ static BMFace *knife_find_common_face(ListBase *faces1, ListBase *faces2) { Ref *ref1, *ref2; @@ -797,12 +1689,13 @@ static KnifeVert *new_knife_vert(KnifeTool_OpData *kcd, const float co[3], const static KnifeEdge *new_knife_edge(KnifeTool_OpData *kcd) { + KnifeEdge *kfe = BLI_mempool_calloc(kcd->kedges); kcd->totkedge++; - return BLI_mempool_calloc(kcd->kedges); + return kfe; } -/* get a KnifeVert wrapper for an existing BMVert */ -static KnifeVert *get_bm_knife_vert(KnifeTool_OpData *kcd, BMVert *v) +/* Get a KnifeVert wrapper for an existing BMVert. */ +static KnifeVert *get_bm_knife_vert(KnifeTool_OpData *kcd, BMVert *v, Object *ob, uint base_index) { KnifeVert *kfv = BLI_ghash_lookup(kcd->origvertmap, v); const float *cageco; @@ -812,13 +1705,20 @@ static KnifeVert *get_bm_knife_vert(KnifeTool_OpData *kcd, BMVert *v) BMFace *f; if (BM_elem_index_get(v) >= 0) { - cageco = kcd->cagecos[BM_elem_index_get(v)]; + cageco = kcd->cagecos[base_index][BM_elem_index_get(v)]; } else { cageco = v->co; } - kfv = new_knife_vert(kcd, v->co, cageco); + + float cageco_ws[3]; + mul_v3_m4v3(cageco_ws, ob->obmat, cageco); + + kfv = new_knife_vert(kcd, v->co, cageco_ws); kfv->v = v; + kfv->ob = ob; + kfv->base_index = base_index; + BLI_ghash_insert(kcd->origvertmap, v, kfv); BM_ITER_ELEM (f, &bmiter, v, BM_FACES_OF_VERT) { knife_append_list(kcd, &kfv->faces, f); @@ -828,8 +1728,8 @@ static KnifeVert *get_bm_knife_vert(KnifeTool_OpData *kcd, BMVert *v) return kfv; } -/* get a KnifeEdge wrapper for an existing BMEdge */ -static KnifeEdge *get_bm_knife_edge(KnifeTool_OpData *kcd, BMEdge *e) +/* Get a KnifeEdge wrapper for an existing BMEdge. */ +static KnifeEdge *get_bm_knife_edge(KnifeTool_OpData *kcd, BMEdge *e, Object *ob, uint base_index) { KnifeEdge *kfe = BLI_ghash_lookup(kcd->origedgemap, e); if (!kfe) { @@ -838,8 +1738,8 @@ static KnifeEdge *get_bm_knife_edge(KnifeTool_OpData *kcd, BMEdge *e) kfe = new_knife_edge(kcd); kfe->e = e; - kfe->v1 = get_bm_knife_vert(kcd, e->v1); - kfe->v2 = get_bm_knife_vert(kcd, e->v2); + kfe->v1 = get_bm_knife_vert(kcd, e->v1, ob, base_index); + kfe->v2 = get_bm_knife_vert(kcd, e->v2, ob, base_index); knife_add_to_vert_edges(kcd, kfe); @@ -853,7 +1753,10 @@ static KnifeEdge *get_bm_knife_edge(KnifeTool_OpData *kcd, BMEdge *e) return kfe; } -static ListBase *knife_get_face_kedges(KnifeTool_OpData *kcd, BMFace *f) +static ListBase *knife_get_face_kedges(KnifeTool_OpData *kcd, + Object *ob, + uint base_index, + BMFace *f) { ListBase *list = BLI_ghash_lookup(kcd->kedgefacemap, f); @@ -864,7 +1767,7 @@ static ListBase *knife_get_face_kedges(KnifeTool_OpData *kcd, BMFace *f) list = knife_empty_list(kcd); BM_ITER_ELEM (e, &bmiter, f, BM_EDGES_OF_FACE) { - knife_append_list(kcd, list, get_bm_knife_edge(kcd, e)); + knife_append_list(kcd, list, get_bm_knife_edge(kcd, e, ob, base_index)); } BLI_ghash_insert(kcd->kedgefacemap, f, list); @@ -875,7 +1778,7 @@ static ListBase *knife_get_face_kedges(KnifeTool_OpData *kcd, BMFace *f) static void knife_edge_append_face(KnifeTool_OpData *kcd, KnifeEdge *kfe, BMFace *f) { - knife_append_list(kcd, knife_get_face_kedges(kcd, f), kfe); + knife_append_list(kcd, knife_get_face_kedges(kcd, kfe->v1->ob, kfe->v1->base_index, f), kfe); knife_append_list(kcd, &kfe->faces, f); } @@ -891,6 +1794,8 @@ static KnifeVert *knife_split_edge(KnifeTool_OpData *kcd, newkfe->v1 = kfe->v1; newkfe->v2 = new_knife_vert(kcd, co, cageco); + newkfe->v2->ob = kfe->v1->ob; + newkfe->v2->base_index = kfe->v1->base_index; newkfe->v2->is_cut = true; if (kfe->e) { knife_add_edge_faces_to_vert(kcd, newkfe->v2, kfe->e); @@ -898,7 +1803,7 @@ static KnifeVert *knife_split_edge(KnifeTool_OpData *kcd, else { /* kfe cuts across an existing face. * If v1 and v2 are in multiple faces together (e.g., if they - * are in doubled polys) then this arbitrarily chooses one of them */ + * are in doubled polys) then this arbitrarily chooses one of them. */ f = knife_find_common_face(&kfe->v1->faces, &kfe->v2->faces); if (f) { knife_append_list(kcd, &newkfe->v2->faces, f); @@ -910,6 +1815,7 @@ static KnifeVert *knife_split_edge(KnifeTool_OpData *kcd, BLI_remlink(&kfe->v1->edges, ref); kfe->v1 = newkfe->v2; + kfe->v1->is_splitting = true; BLI_addtail(&kfe->v1->edges, ref); for (ref = kfe->faces.first; ref; ref = ref->next) { @@ -921,11 +1827,32 @@ static KnifeVert *knife_split_edge(KnifeTool_OpData *kcd, newkfe->is_cut = kfe->is_cut; newkfe->e = kfe->e; + newkfe->splits++; + kfe->splits++; + + kcd->undo->splits++; + + BLI_stack_push(kcd->splitstack, (void *)&kfe); + BLI_stack_push(kcd->splitstack, (void *)&newkfe); + *r_kfe = newkfe; return newkfe->v2; } +/* Rejoin two edges split by #knife_split_edge. */ +static void knife_join_edge(KnifeEdge *newkfe, KnifeEdge *kfe) +{ + newkfe->is_invalid = true; + newkfe->v2->is_invalid = true; + + kfe->v1 = newkfe->v1; + + kfe->splits--; + kfe->v1->is_splitting = false; + kfe->v2->is_splitting = false; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -937,18 +1864,19 @@ static KnifeVert *knife_split_edge(KnifeTool_OpData *kcd, static void knife_start_cut(KnifeTool_OpData *kcd) { kcd->prev = kcd->curr; - kcd->curr.is_space = 0; /* TODO: why do we do this? */ + kcd->curr.is_space = 0; /* TODO: Why do we do this? */ + kcd->mdata.is_stored = false; if (kcd->prev.vert == NULL && kcd->prev.edge == NULL) { float origin[3], origin_ofs[3]; float ofs_local[3]; negate_v3_v3(ofs_local, kcd->vc.rv3d->ofs); - mul_m4_v3(kcd->ob_imat, ofs_local); knife_input_ray_segment(kcd, kcd->curr.mval, 1.0f, origin, origin_ofs); - if (!isect_line_plane_v3(kcd->prev.cage, origin, origin_ofs, ofs_local, kcd->proj_zaxis)) { + if (!isect_line_plane_v3( + kcd->prev.cage, origin, origin_ofs, ofs_local, kcd->vc.rv3d->viewinv[2])) { zero_v3(kcd->prev.cage); } @@ -968,9 +1896,9 @@ static void linehit_to_knifepos(KnifePosData *kpos, KnifeLineHit *lh) copy_v2_v2(kpos->mval, lh->schit); } -/* primary key: lambda along cut - * secondary key: lambda along depth - * tertiary key: pointer comparisons of verts if both snapped to verts +/* Primary key: lambda along cut + * Secondary key: lambda along depth + * Tertiary key: pointer comparisons of verts if both snapped to verts */ static int linehit_compare(const void *vlh1, const void *vlh2) { @@ -1049,19 +1977,19 @@ static void prepare_linehits_for_cut(KnifeTool_OpData *kcd) } if (is_double) { - /* delete-in-place loop: copying from pos j to pos i+1 */ + /* Delete-in-place loop: copying from pos j to pos i+1. */ int i = 0; int j = 1; while (j < n) { KnifeLineHit *lhi = &linehits[i]; KnifeLineHit *lhj = &linehits[j]; if (lhj->l == -1.0f) { - j++; /* skip copying this one */ + j++; /* Skip copying this one. */ } else { - /* copy unless a no-op */ + /* Copy unless a no-op. */ if (lhi->l == -1.0f) { - /* could happen if linehits[0] is being deleted */ + /* Could happen if linehits[0] is being deleted. */ memcpy(&linehits[i], &linehits[j], sizeof(KnifeLineHit)); } else { @@ -1077,7 +2005,7 @@ static void prepare_linehits_for_cut(KnifeTool_OpData *kcd) } } -/* Add hit to list of hits in facehits[f], where facehits is a map, if not already there */ +/* Add hit to list of hits in facehits[f], where facehits is a map, if not already there. */ static void add_hit_to_facehits(KnifeTool_OpData *kcd, GHash *facehits, BMFace *f, @@ -1093,8 +2021,8 @@ static void add_hit_to_facehits(KnifeTool_OpData *kcd, } /** - * special purpose function, if the linehit is connected to a real edge/vert - * return true if \a co is outside the face. + * Special purpose function, if the linehit is connected to a real edge/vert. + * Return true if \a co is outside the face. */ static bool knife_add_single_cut__is_linehit_outside_face(BMFace *f, const KnifeLineHit *lh, @@ -1131,12 +2059,9 @@ static void knife_add_single_cut(KnifeTool_OpData *kcd, return; } - /* if the cut is on an edge, just tag that its a cut and return */ + /* If the cut is on an edge. */ if ((lh1->v && lh2->v) && (lh1->v->v && lh2->v && lh2->v->v) && (e_base = BM_edge_exists(lh1->v->v, lh2->v->v))) { - kfe = get_bm_knife_edge(kcd, e_base); - kfe->is_cut = true; - kfe->e = e_base; return; } if (knife_add_single_cut__is_linehit_outside_face(f, lh1, lh2->hit) || @@ -1144,7 +2069,7 @@ static void knife_add_single_cut(KnifeTool_OpData *kcd, return; } - /* Check if edge actually lies within face (might not, if this face is concave) */ + /* Check if edge actually lies within face (might not, if this face is concave). */ if ((lh1->v && !lh1->kfe) && (lh2->v && !lh2->kfe)) { if (!knife_verts_edge_in_face(lh1->v, lh2->v, f)) { return; @@ -1165,6 +2090,8 @@ static void knife_add_single_cut(KnifeTool_OpData *kcd, else { BLI_assert(lh1->f); kfe->v1 = new_knife_vert(kcd, lh1->hit, lh1->cagehit); + kfe->v1->ob = lh1->ob; + kfe->v1->base_index = lh1->base_index; kfe->v1->is_cut = true; kfe->v1->is_face = true; knife_append_list(kcd, &kfe->v1->faces, lh1->f); @@ -1176,23 +2103,27 @@ static void knife_add_single_cut(KnifeTool_OpData *kcd, } else if (lh2->kfe) { kfe->v2 = knife_split_edge(kcd, lh2->kfe, lh2->hit, lh2->cagehit, &kfe2); - lh2->v = kfe->v2; /* future uses of lh2 won't split again */ + lh2->v = kfe->v2; /* Future uses of lh2 won't split again. */ } else { BLI_assert(lh2->f); kfe->v2 = new_knife_vert(kcd, lh2->hit, lh2->cagehit); + kfe->v2->ob = lh2->ob; + kfe->v2->base_index = lh2->base_index; kfe->v2->is_cut = true; kfe->v2->is_face = true; knife_append_list(kcd, &kfe->v2->faces, lh2->f); - lh2->v = kfe->v2; /* record the KnifeVert for this hit */ + lh2->v = kfe->v2; /* Record the KnifeVert for this hit. */ } knife_add_to_vert_edges(kcd, kfe); - /* TODO: check if this is ever needed */ if (kfe->basef && !find_ref(&kfe->faces, kfe->basef)) { knife_edge_append_face(kcd, kfe, kfe->basef); } + + /* Update current undo frame cut count. */ + kcd->undo->cuts++; } /* Given a list of KnifeLineHits for one face, sorted by l @@ -1212,9 +2143,8 @@ static void knife_cut_face(KnifeTool_OpData *kcd, BMFace *f, ListBase *hits) } } -static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfedges) +static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMesh *bm, BMFace *f, ListBase *kfedges) { - BMesh *bm = kcd->em->bm; KnifeEdge *kfe; Ref *ref; int edge_array_len = BLI_listbase_count(kfedges); @@ -1222,7 +2152,7 @@ static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfe BMEdge **edge_array = BLI_array_alloca(edge_array, edge_array_len); - /* point to knife edges we've created edges in, edge_array aligned */ + /* Point to knife edges we've created edges in, edge_array aligned. */ KnifeEdge **kfe_array = BLI_array_alloca(kfe_array, edge_array_len); BLI_assert(BLI_gset_len(kcd->edgenet.edge_visit) == 0); @@ -1232,6 +2162,10 @@ static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfe bool is_new_edge = false; kfe = ref->ref; + if (kfe->is_invalid) { + continue; + } + if (kfe->e == NULL) { if (kfe->v1->v && kfe->v2->v) { kfe->e = BM_edge_exists(kfe->v1->v, kfe->v2->v); @@ -1240,7 +2174,7 @@ static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfe if (kfe->e) { if (BM_edge_in_face(kfe->e, f)) { - /* shouldn't happen, but in this case - just ignore */ + /* Shouldn't happen, but in this case just ignore. */ continue; } } @@ -1292,7 +2226,7 @@ static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfe } edge_array_len = edge_array_holes_len; - edge_array = edge_array_holes; /* owned by the arena */ + edge_array = edge_array_holes; /* Owned by the arena. */ } #endif @@ -1307,7 +2241,7 @@ static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfe } } - /* remove dangling edges, not essential - but nice for users */ + /* Remove dangling edges, not essential - but nice for users. */ for (i = 0; i < edge_array_len_orig; i++) { if (kfe_array[i]) { if (BM_edge_is_wire(kfe_array[i]->e)) { @@ -1342,10 +2276,11 @@ static int sort_verts_by_dist_cb(void *co_p, const void *cur_a_p, const void *cu return 0; } -/* Use the network of KnifeEdges and KnifeVerts accumulated to make real BMVerts and BMEdedges */ -static void knife_make_cuts(KnifeTool_OpData *kcd) +/* Use the network of KnifeEdges and KnifeVerts accumulated to make real BMVerts and BMEdedges. */ +static void knife_make_cuts(KnifeTool_OpData *kcd, Object *ob) { - BMesh *bm = kcd->em->bm; + BMEditMesh *em = BKE_editmesh_from_object(ob); + BMesh *bm = em->bm; KnifeEdge *kfe; KnifeVert *kfv; BMFace *f; @@ -1361,11 +2296,14 @@ static void knife_make_cuts(KnifeTool_OpData *kcd) BLI_smallhash_init(fhash); BLI_smallhash_init(ehash); - /* put list of cutting edges for a face into fhash, keyed by face */ + /* Put list of cutting edges for a face into fhash, keyed by face. */ BLI_mempool_iternew(kcd->kedges, &iter); for (kfe = BLI_mempool_iterstep(&iter); kfe; kfe = BLI_mempool_iterstep(&iter)) { + if (kfe->is_invalid || kfe->v1->ob != ob) { + continue; + } - /* select edges that lie directly on the cut */ + /* Select edges that lie directly on the cut. */ if (kcd->select_result) { if (kfe->e && kfe->is_cut) { BM_edge_select_set(bm, kfe->e, true); @@ -1384,11 +2322,11 @@ static void knife_make_cuts(KnifeTool_OpData *kcd) knife_append_list(kcd, list, kfe); } - /* put list of splitting vertices for an edge into ehash, keyed by edge */ + /* Put list of splitting vertices for an edge into ehash, keyed by edge. */ BLI_mempool_iternew(kcd->kverts, &iter); for (kfv = BLI_mempool_iterstep(&iter); kfv; kfv = BLI_mempool_iterstep(&iter)) { - if (kfv->v) { - continue; /* already have a BMVert */ + if (kfv->v || kfv->is_invalid || kfv->ob != ob) { + continue; /* Already have a BMVert. */ } for (ref = kfv->edges.first; ref; ref = ref->next) { kfe = ref->ref; @@ -1401,14 +2339,14 @@ static void knife_make_cuts(KnifeTool_OpData *kcd) list = knife_empty_list(kcd); BLI_smallhash_insert(ehash, (uintptr_t)e, list); } - /* there can be more than one kfe in kfv's list with same e */ + /* There can be more than one kfe in kfv's list with same e. */ if (!find_ref(list, kfv)) { knife_append_list(kcd, list, kfv); } } } - /* split bmesh edges where needed */ + /* Split bmesh edges where needed. */ for (list = BLI_smallhash_iternew(ehash, &hiter, (uintptr_t *)&e); list; list = BLI_smallhash_iternext(&hiter, (uintptr_t *)&e)) { BLI_listbase_sort_r(list, sort_verts_by_dist_cb, e->v1->co); @@ -1421,13 +2359,13 @@ static void knife_make_cuts(KnifeTool_OpData *kcd) } if (kcd->only_select) { - EDBM_flag_disable_all(kcd->em, BM_ELEM_SELECT); + EDBM_flag_disable_all(em, BM_ELEM_SELECT); } - /* do cuts for each face */ + /* Do cuts for each face. */ for (list = BLI_smallhash_iternew(fhash, &hiter, (uintptr_t *)&f); list; list = BLI_smallhash_iternext(&hiter, (uintptr_t *)&f)) { - knife_make_face_cuts(kcd, f, list); + knife_make_face_cuts(kcd, bm, f, list); } BLI_smallhash_release(fhash); @@ -1448,6 +2386,21 @@ static void knife_add_cut(KnifeTool_OpData *kcd) GHashIterator giter; ListBase *list; + /* Allocate new undo frame on stack, unless cut is being dragged. */ + if (!kcd->is_drag_undo) { + kcd->undo = BLI_stack_push_r(kcd->undostack); + kcd->undo->pos = kcd->prev; + kcd->undo->cuts = 0; + kcd->undo->splits = 0; + kcd->undo->mdata = kcd->mdata; + kcd->is_drag_undo = true; + } + + /* Save values for angle drawing calculations. */ + copy_v3_v3(kcd->mdata.cage, kcd->mdata.corr_prev_cage); + copy_v2_v2(kcd->mdata.mval, kcd->prev.mval); + kcd->mdata.is_stored = true; + prepare_linehits_for_cut(kcd); if (kcd->totlinehit == 0) { if (kcd->is_drag_hold == false) { @@ -1456,7 +2409,12 @@ static void knife_add_cut(KnifeTool_OpData *kcd) return; } - /* make facehits: map face -> list of linehits touching it */ + /* Consider most recent linehit in angle drawing calculations. */ + if (kcd->totlinehit >= 2) { + copy_v3_v3(kcd->mdata.cage, kcd->linehits[kcd->totlinehit - 2].cagehit); + } + + /* Make facehits: map face -> list of linehits touching it. */ facehits = BLI_ghash_ptr_new("knife facehits"); for (i = 0; i < kcd->totlinehit; i++) { KnifeLineHit *lh = &kcd->linehits[i]; @@ -1485,11 +2443,11 @@ static void knife_add_cut(KnifeTool_OpData *kcd) knife_cut_face(kcd, f, list); } - /* set up for next cut */ + /* Set up for next cut. */ kcd->prev = kcd->curr; if (kcd->prev.bmface) { - /* was "in face" but now we have a KnifeVert it is snapped to */ + /* Was "in face" but now we have a KnifeVert it is snapped to. */ KnifeLineHit *lh = &kcd->linehits[kcd->totlinehit - 1]; kcd->prev.vert = lh->v; kcd->prev.bmface = NULL; @@ -1529,7 +2487,7 @@ static void knife_finish_cut(KnifeTool_OpData *kcd) * to hash lookup routine; will reverse this in the get routine. * Doing this lazily rather than all at once for all faces. */ -static void set_lowest_face_tri(KnifeTool_OpData *kcd, BMFace *f, int index) +static void set_lowest_face_tri(KnifeTool_OpData *kcd, BMEditMesh *em, BMFace *f, int index) { int i; @@ -1537,10 +2495,10 @@ static void set_lowest_face_tri(KnifeTool_OpData *kcd, BMFace *f, int index) return; } - BLI_assert(index >= 0 && index < kcd->em->tottri); - BLI_assert(kcd->em->looptris[index][0]->f == f); + BLI_assert(index >= 0 && index < em->tottri); + BLI_assert(em->looptris[index][0]->f == f); for (i = index - 1; i >= 0; i--) { - if (kcd->em->looptris[i][0]->f != f) { + if (em->looptris[i][0]->f != f) { i++; break; } @@ -1552,7 +2510,8 @@ static void set_lowest_face_tri(KnifeTool_OpData *kcd, BMFace *f, int index) BLI_ghash_insert(kcd->facetrimap, f, POINTER_FROM_INT(i + 1)); } -/* This should only be called for faces that have had a lowest face tri set by previous function */ +/* This should only be called for faces that have had a lowest face tri set by previous function. + */ static int get_lowest_face_tri(KnifeTool_OpData *kcd, BMFace *f) { int ans; @@ -1574,11 +2533,15 @@ static bool knife_ray_intersect_face(KnifeTool_OpData *kcd, const float s[2], const float v1[3], const float v2[3], + Object *ob, + uint base_index, BMFace *f, const float face_tol_sq, float hit_co[3], float hit_cageco[3]) { + BMEditMesh *em = BKE_editmesh_from_object(ob); + int tottri, tri_i; float raydir[3]; float tri_norm[3], tri_plane[4]; @@ -1592,37 +2555,45 @@ static bool knife_ray_intersect_face(KnifeTool_OpData *kcd, sub_v3_v3v3(raydir, v2, v1); normalize_v3(raydir); tri_i = get_lowest_face_tri(kcd, f); - tottri = kcd->em->tottri; + tottri = em->tottri; BLI_assert(tri_i >= 0 && tri_i < tottri); for (; tri_i < tottri; tri_i++) { - const float *lv1, *lv2, *lv3; + float lv[3][3]; float ray_tri_uv[2]; - tri = kcd->em->looptris[tri_i]; + tri = em->looptris[tri_i]; if (tri[0]->f != f) { break; } - lv1 = kcd->cagecos[BM_elem_index_get(tri[0]->v)]; - lv2 = kcd->cagecos[BM_elem_index_get(tri[1]->v)]; - lv3 = kcd->cagecos[BM_elem_index_get(tri[2]->v)]; - /* using epsilon test in case ray is directly through an internal + copy_v3_v3(lv[0], kcd->cagecos[base_index][BM_elem_index_get(tri[0]->v)]); + copy_v3_v3(lv[1], kcd->cagecos[base_index][BM_elem_index_get(tri[1]->v)]); + copy_v3_v3(lv[2], kcd->cagecos[base_index][BM_elem_index_get(tri[2]->v)]); + mul_m4_v3(ob->obmat, lv[0]); + mul_m4_v3(ob->obmat, lv[1]); + mul_m4_v3(ob->obmat, lv[2]); + + /* Using epsilon test in case ray is directly through an internal * tessellation edge and might not hit either tessellation tri with * an exact test; - * we will exclude hits near real edges by a later test */ - if (isect_ray_tri_epsilon_v3(v1, raydir, lv1, lv2, lv3, &lambda, ray_tri_uv, KNIFE_FLT_EPS)) { - /* check if line coplanar with tri */ - normal_tri_v3(tri_norm, lv1, lv2, lv3); - plane_from_point_normal_v3(tri_plane, lv1, tri_norm); + * We will exclude hits near real edges by a later test. */ + if (isect_ray_tri_epsilon_v3( + v1, raydir, lv[0], lv[1], lv[2], &lambda, ray_tri_uv, KNIFE_FLT_EPS)) { + /* Check if line coplanar with tri. */ + normal_tri_v3(tri_norm, lv[0], lv[1], lv[2]); + plane_from_point_normal_v3(tri_plane, lv[0], tri_norm); if ((dist_squared_to_plane_v3(v1, tri_plane) < KNIFE_FLT_EPS) && (dist_squared_to_plane_v3(v2, tri_plane) < KNIFE_FLT_EPS)) { return false; } - interp_v3_v3v3v3_uv(hit_cageco, lv1, lv2, lv3, ray_tri_uv); - /* Now check that far enough away from verts and edges */ - list = knife_get_face_kedges(kcd, f); + interp_v3_v3v3v3_uv(hit_cageco, lv[0], lv[1], lv[2], ray_tri_uv); + /* Now check that far enough away from verts and edges. */ + list = knife_get_face_kedges(kcd, ob, base_index, f); for (ref = list->first; ref; ref = ref->next) { kfe = ref->ref; + if (kfe->is_invalid) { + continue; + } knife_project_v2(kcd, kfe->v1->cageco, se1); knife_project_v2(kcd, kfe->v2->cageco, se2); d = dist_squared_to_line_segment_v2(s, se1, se2); @@ -1642,19 +2613,24 @@ static bool knife_ray_intersect_face(KnifeTool_OpData *kcd, */ static void calc_ortho_extent(KnifeTool_OpData *kcd) { + Object *ob; + BMEditMesh *em; BMIter iter; BMVert *v; - BMesh *bm = kcd->em->bm; float min[3], max[3]; - INIT_MINMAX(min, max); - if (kcd->cagecos) { - minmax_v3v3_v3_array(min, max, kcd->cagecos, bm->totvert); - } - else { - BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { - minmax_v3v3_v3(min, max, v->co); + for (uint b = 0; b < kcd->objects_len; b++) { + ob = kcd->objects[b]; + em = BKE_editmesh_from_object(ob); + + if (kcd->cagecos[b]) { + minmax_v3v3_v3_array(min, max, kcd->cagecos[b], em->bm->totvert); + } + else { + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + minmax_v3v3_v3(min, max, v->co); + } } } @@ -1732,28 +2708,26 @@ static bool point_is_visible(KnifeTool_OpData *kcd, { BMFace *f_hit; - /* If box clipping on, make sure p is not clipped */ + /* If box clipping on, make sure p is not clipped. */ if (RV3D_CLIPPING_ENABLED(kcd->vc.v3d, kcd->vc.rv3d) && - ED_view3d_clipping_test(kcd->vc.rv3d, p, true)) { + ED_view3d_clipping_test(kcd->vc.rv3d, p, false)) { return false; } - /* If not cutting through, make sure no face is in front of p */ + /* If not cutting through, make sure no face is in front of p. */ if (!kcd->cut_through) { float dist; float view[3], p_ofs[3]; - /* TODO: I think there's a simpler way to get the required raycast ray */ + /* TODO: I think there's a simpler way to get the required raycast ray. */ ED_view3d_unproject_v3(kcd->vc.region, s[0], s[1], 0.0f, view); - mul_m4_v3(kcd->ob_imat, view); - - /* make p_ofs a little towards view, so ray doesn't hit p's face. */ + /* Make p_ofs a little towards view, so ray doesn't hit p's face. */ sub_v3_v3(view, p); dist = normalize_v3(view); copy_v3_v3(p_ofs, p); - /* avoid projecting behind the viewpoint */ + /* Avoid projecting behind the viewpoint. */ if (kcd->is_ortho && (kcd->vc.rv3d->persp != RV3D_CAMOB)) { dist = kcd->vc.v3d->clip_end * 2.0f; } @@ -1774,20 +2748,21 @@ static bool point_is_visible(KnifeTool_OpData *kcd, } } - /* see if there's a face hit between p1 and the view */ + /* See if there's a face hit between p1 and the view. */ if (ele_test) { - f_hit = BKE_bmbvh_ray_cast_filter(kcd->bmbvh, - p_ofs, - view, - KNIFE_FLT_EPS, - &dist, - NULL, - NULL, - bm_ray_cast_cb_elem_not_in_face_check, - ele_test); + f_hit = knife_bvh_raycast_filter(kcd, + p_ofs, + view, + KNIFE_FLT_EPS, + &dist, + NULL, + NULL, + NULL, + bm_ray_cast_cb_elem_not_in_face_check, + ele_test); } else { - f_hit = BKE_bmbvh_ray_cast(kcd->bmbvh, p_ofs, view, KNIFE_FLT_EPS, &dist, NULL, NULL); + f_hit = knife_bvh_raycast(kcd, p_ofs, view, KNIFE_FLT_EPS, &dist, NULL, NULL, NULL); } if (f_hit) { @@ -1799,7 +2774,7 @@ static bool point_is_visible(KnifeTool_OpData *kcd, } /* Clip the line (v1, v2) to planes perpendicular to it and distances d from - * the closest point on the line to the origin */ + * the closest point on the line to the origin. */ static void clip_to_ortho_planes(float v1[3], float v2[3], const float center[3], const float d) { float closest[3], dir[3]; @@ -1821,12 +2796,12 @@ static void set_linehit_depth(KnifeTool_OpData *kcd, KnifeLineHit *lh) lh->m = dot_m4_v3_row_z(kcd->vc.rv3d->persmatob, lh->cagehit); } -/* Finds visible (or all, if cutting through) edges that intersects the current screen drag line */ +/* Finds visible (or all, if cutting through) edges that intersects the current screen drag line. + */ static void knife_find_line_hits(KnifeTool_OpData *kcd) { - SmallHash faces, kfes, kfvs; + SmallHash faces, kfes, kfvs, fobs; float v1[3], v2[3], v3[3], v4[3], s1[2], s2[2]; - BVHTree *tree; int *results, *result; BMLoop **ls; BMFace *f; @@ -1858,7 +2833,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) copy_v3_v3(v1, kcd->prev.cage); copy_v3_v3(v2, kcd->curr.cage); - /* project screen line's 3d coordinates back into 2d */ + /* Project screen line's 3d coordinates back into 2d. */ knife_project_v2(kcd, v1, s1); knife_project_v2(kcd, v2, s2); @@ -1873,22 +2848,17 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) } } - /* unproject screen line */ + /* Unproject screen line. */ ED_view3d_win_to_segment_clipped(kcd->vc.depsgraph, kcd->region, kcd->vc.v3d, s1, v1, v3, true); ED_view3d_win_to_segment_clipped(kcd->vc.depsgraph, kcd->region, kcd->vc.v3d, s2, v2, v4, true); - mul_m4_v3(kcd->ob_imat, v1); - mul_m4_v3(kcd->ob_imat, v2); - mul_m4_v3(kcd->ob_imat, v3); - mul_m4_v3(kcd->ob_imat, v4); - /* Numeric error, 'v1' -> 'v2', 'v2' -> 'v4' * can end up being ~2000 units apart with an orthogonal perspective. * * (from ED_view3d_win_to_segment_clipped() above) - * this gives precision error; rather than solving properly + * This gives precision error; rather than solving properly * (which may involve using doubles everywhere!), - * limit the distance between these points */ + * limit the distance between these points. */ if (kcd->is_ortho && (kcd->vc.rv3d->persp != RV3D_CAMOB)) { if (kcd->ortho_extent == 0.0f) { calc_ortho_extent(kcd); @@ -1910,8 +2880,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) * intersect the cut plane with rays v1-v3 and v2-v4. * This de-duplicates the candidates before doing more expensive intersection tests. */ - tree = BKE_bmbvh_tree_get(kcd->bmbvh); - results = BLI_bvhtree_intersect_plane(tree, plane, &tot); + results = BLI_bvhtree_intersect_plane(kcd->bvh.tree, plane, &tot); if (!results) { return; } @@ -1919,26 +2888,44 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) BLI_smallhash_init(&faces); BLI_smallhash_init(&kfes); BLI_smallhash_init(&kfvs); + BLI_smallhash_init(&fobs); + + Object *ob; + BMEditMesh *em; + uint b = 0; for (i = 0, result = results; i < tot; i++, result++) { - ls = (BMLoop **)kcd->em->looptris[*result]; + for (b = 0; b < kcd->objects_len; b++) { + ob = kcd->objects[b]; + em = BKE_editmesh_from_object(ob); + if (*result >= 0 && *result < em->tottri) { + ls = (BMLoop **)em->looptris[*result]; + break; + } + *result -= em->tottri; + } + f = ls[0]->f; - set_lowest_face_tri(kcd, f, *result); + set_lowest_face_tri(kcd, em, f, *result); - /* occlude but never cut unselected faces (when only_select is used) */ + /* Occlude but never cut unselected faces (when only_select is used). */ if (kcd->only_select && !BM_elem_flag_test(f, BM_ELEM_SELECT)) { continue; } - /* for faces, store index of lowest hit looptri in hash */ + /* For faces, store index of lowest hit looptri in hash. */ if (BLI_smallhash_haskey(&faces, (uintptr_t)f)) { continue; } - /* don't care what the value is except that it is non-NULL, for iterator */ + /* Don't care what the value is except that it is non-NULL, for iterator. */ BLI_smallhash_insert(&faces, (uintptr_t)f, f); + BLI_smallhash_insert(&fobs, (uintptr_t)f, (void *)(uintptr_t)b); - list = knife_get_face_kedges(kcd, f); + list = knife_get_face_kedges(kcd, ob, b, f); for (ref = list->first; ref; ref = ref->next) { kfe = ref->ref; + if (kfe->is_invalid) { + continue; + } if (BLI_smallhash_haskey(&kfes, (uintptr_t)kfe)) { continue; } @@ -1950,7 +2937,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) } } - /* Now go through the candidates and find intersections */ + /* Now go through the candidates and find intersections. */ /* These tolerances, in screen space, are for intermediate hits, * as ends are already snapped to screen. */ @@ -1971,9 +2958,9 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) line_tol_sq = line_tol * line_tol; face_tol_sq = face_tol * face_tol; - /* Assume these tolerances swamp floating point rounding errors in calculations below */ + /* Assume these tolerances swamp floating point rounding errors in calculations below. */ - /* first look for vertex hits */ + /* First look for vertex hits. */ for (val_p = BLI_smallhash_iternew_p(&kfvs, &hiter, (uintptr_t *)&v); val_p; val_p = BLI_smallhash_iternext_p(&hiter, (uintptr_t *)&v)) { KnifeEdge *kfe_hit = NULL; @@ -2001,12 +2988,14 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) hit.v = v; /* If this isn't from an existing BMVert, it may have been added to a BMEdge originally. - * knowing if the hit comes from an edge is important for edge-in-face checks later on - * see: #knife_add_single_cut -> #knife_verts_edge_in_face, T42611 */ + * Knowing if the hit comes from an edge is important for edge-in-face checks later on. + * See: #knife_add_single_cut -> #knife_verts_edge_in_face, T42611. */ if (kfe_hit) { hit.kfe = kfe_hit; } + hit.ob = v->ob; + hit.base_index = v->base_index; copy_v3_v3(hit.hit, v->co); copy_v3_v3(hit.cagehit, v->cageco); copy_v2_v2(hit.schit, s); @@ -2021,16 +3010,10 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) } } - /* now edge hits; don't add if a vertex at end of edge should have hit */ + /* Now edge hits; don't add if a vertex at end of edge should have hit. */ for (val = BLI_smallhash_iternew(&kfes, &hiter, (uintptr_t *)&kfe); val; val = BLI_smallhash_iternext(&hiter, (uintptr_t *)&kfe)) { - /* If we intersect any of the vertices, don't attempt to intersect the edge. */ - if (BLI_smallhash_lookup(&kfvs, (intptr_t)kfe->v1) || - BLI_smallhash_lookup(&kfvs, (intptr_t)kfe->v2)) { - continue; - } - knife_project_v2(kcd, kfe->v1->cageco, se1); knife_project_v2(kcd, kfe->v2->cageco, se2); int isect_kind = 1; @@ -2045,7 +3028,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) else { isect_kind = isect_seg_seg_v2_point_ex(s1, s2, se1, se2, 0.0f, sint); if (isect_kind == -1) { - /* isect_seg_seg_v2_point doesn't do tolerance test around ends of s1-s2 */ + /* isect_seg_seg_v2_point doesn't do tolerance test around ends of s1-s2. */ closest_to_line_segment_v2(sint, s1, se1, se2); if (len_squared_v2v2(sint, s1) <= line_tol_sq) { isect_kind = 1; @@ -2066,14 +3049,14 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) lambda = d1 / d2; /* Can't just interpolate between ends of kfe because * that doesn't work with perspective transformation. - * Need to find 3d intersection of ray through sint */ + * Need to find 3d intersection of ray through sint. */ knife_input_ray_segment(kcd, sint, 1.0f, r1, r2); isect_kind = isect_line_line_v3( kfe->v1->cageco, kfe->v2->cageco, r1, r2, p_cage, p_cage_tmp); if (isect_kind >= 1 && point_is_visible(kcd, p_cage, sint, bm_elem_from_knife_edge(kfe))) { memset(&hit, 0, sizeof(hit)); if (kcd->snap_midpoints) { - /* choose intermediate point snap too */ + /* Choose intermediate point snap too. */ mid_v3_v3v3(p_cage, kfe->v1->cageco, kfe->v2->cageco); mid_v2_v2v2(sint, se1, se2); lambda = 0.5f; @@ -2081,6 +3064,8 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) hit.kfe = kfe; transform_point_by_seg_v3( hit.hit, p_cage, kfe->v1->co, kfe->v2->co, kfe->v1->cageco, kfe->v2->cageco); + hit.ob = kfe->v1->ob; + hit.base_index = kfe->v1->base_index; copy_v3_v3(hit.cagehit, p_cage); copy_v2_v2(hit.schit, sint); hit.perc = lambda; @@ -2091,7 +3076,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) } } - /* now face hits; don't add if a vertex or edge in face should have hit */ + /* Now face hits; don't add if a vertex or edge in face should have hit. */ const bool use_hit_prev = (kcd->prev.vert == NULL) && (kcd->prev.edge == NULL); const bool use_hit_curr = (kcd->curr.vert == NULL) && (kcd->curr.edge == NULL) && !kcd->is_drag_hold; @@ -2100,10 +3085,16 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) val = BLI_smallhash_iternext(&hiter, (uintptr_t *)&f)) { float p[3], p_cage[3]; - if (use_hit_prev && knife_ray_intersect_face(kcd, s1, v1, v3, f, face_tol_sq, p, p_cage)) { + uint base_index = (uint)(uintptr_t)BLI_smallhash_lookup(&fobs, (uintptr_t)f); + ob = kcd->objects[base_index]; + + if (use_hit_prev && + knife_ray_intersect_face(kcd, s1, v1, v3, ob, base_index, f, face_tol_sq, p, p_cage)) { if (point_is_visible(kcd, p_cage, s1, (BMElem *)f)) { memset(&hit, 0, sizeof(hit)); hit.f = f; + hit.ob = ob; + hit.base_index = base_index; copy_v3_v3(hit.hit, p); copy_v3_v3(hit.cagehit, p_cage); copy_v2_v2(hit.schit, s1); @@ -2112,10 +3103,13 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) } } - if (use_hit_curr && knife_ray_intersect_face(kcd, s2, v2, v4, f, face_tol_sq, p, p_cage)) { + if (use_hit_curr && + knife_ray_intersect_face(kcd, s2, v2, v4, ob, base_index, f, face_tol_sq, p, p_cage)) { if (point_is_visible(kcd, p_cage, s2, (BMElem *)f)) { memset(&hit, 0, sizeof(hit)); hit.f = f; + hit.ob = ob; + hit.base_index = base_index; copy_v3_v3(hit.hit, p); copy_v3_v3(hit.cagehit, p_cage); copy_v2_v2(hit.schit, s2); @@ -2129,7 +3123,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) kcd->linehits = linehits; kcd->totlinehit = BLI_array_len(linehits); - /* find position along screen line, used for sorting */ + /* Find position along screen line, used for sorting. */ for (i = 0; i < kcd->totlinehit; i++) { KnifeLineHit *lh = kcd->linehits + i; @@ -2139,6 +3133,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd) BLI_smallhash_release(&faces); BLI_smallhash_release(&kfes); BLI_smallhash_release(&kfvs); + BLI_smallhash_release(&fobs); MEM_freeN(results); } @@ -2165,9 +3160,11 @@ static void knife_pos_data_clear(KnifePosData *kpd) * \{ */ static BMFace *knife_find_closest_face(KnifeTool_OpData *kcd, - float co[3], - float cageco[3], - bool *is_space) + Object **r_ob, + uint *r_base_index, + bool *is_space, + float r_co[3], + float r_cageco[3]) { BMFace *f; float dist = KMAXDIST; @@ -2175,12 +3172,12 @@ static BMFace *knife_find_closest_face(KnifeTool_OpData *kcd, float origin_ofs[3]; float ray[3], ray_normal[3]; - /* unproject to find view ray */ + /* Unproject to find view ray. */ knife_input_ray_segment(kcd, kcd->curr.mval, 1.0f, origin, origin_ofs); sub_v3_v3v3(ray, origin_ofs, origin); normalize_v3_v3(ray_normal, ray); - f = BKE_bmbvh_ray_cast(kcd->bmbvh, origin, ray_normal, 0.0f, NULL, co, cageco); + f = knife_bvh_raycast(kcd, origin, ray_normal, 0.0f, NULL, r_co, r_cageco, r_base_index); if (f && kcd->only_select && BM_elem_flag_test(f, BM_ELEM_SELECT) == 0) { f = NULL; @@ -2190,7 +3187,10 @@ static BMFace *knife_find_closest_face(KnifeTool_OpData *kcd, *is_space = !f; } - if (!f) { + if (f) { + *r_ob = kcd->objects[*r_base_index]; + } + else { if (kcd->is_interactive) { /* Try to use back-buffer selection method if ray casting failed. * @@ -2202,12 +3202,12 @@ static BMFace *knife_find_closest_face(KnifeTool_OpData *kcd, f = EDBM_face_find_nearest(&vc, &dist); - /* cheat for now; just put in the origin instead + /* Cheat for now; just put in the origin instead * of a true coordinate on the face. * This just puts a point 1.0f in front of the view. */ - add_v3_v3v3(co, origin, ray); + add_v3_v3v3(r_co, origin, ray); /* Use this value for the cage location too as it's used to find near edges/vertices. */ - copy_v3_v3(cageco, co); + copy_v3_v3(r_cageco, r_co); } } @@ -2222,6 +3222,8 @@ static BMFace *knife_find_closest_face(KnifeTool_OpData *kcd, */ static int knife_sample_screen_density_from_closest_face(KnifeTool_OpData *kcd, const float radius, + Object *ob, + uint base_index, BMFace *f, const float cageco[3]) { @@ -2234,21 +3236,29 @@ static int knife_sample_screen_density_from_closest_face(KnifeTool_OpData *kcd, knife_project_v2(kcd, cageco, sco); - list = knife_get_face_kedges(kcd, f); + list = knife_get_face_kedges(kcd, ob, base_index, f); for (ref = list->first; ref; ref = ref->next) { KnifeEdge *kfe = ref->ref; int i; + if (kfe->is_invalid) { + continue; + } + for (i = 0; i < 2; i++) { KnifeVert *kfv = i ? kfe->v2 : kfe->v1; float kfv_sco[2]; + if (kfv->is_invalid) { + continue; + } + knife_project_v2(kcd, kfv->cageco, kfv_sco); dis_sq = len_squared_v2v2(kfv_sco, sco); if (dis_sq < radius_sq) { if (RV3D_CLIPPING_ENABLED(kcd->vc.v3d, kcd->vc.rv3d)) { - if (ED_view3d_clipping_test(kcd->vc.rv3d, kfv->cageco, true) == 0) { + if (ED_view3d_clipping_test(kcd->vc.rv3d, kfv->cageco, false) == 0) { c++; } } @@ -2275,27 +3285,27 @@ static float knife_snap_size(KnifeTool_OpData *kcd, float maxsize) if (!kcd->curr.is_space) { density = (float)knife_sample_screen_density_from_closest_face( - kcd, maxsize * 2.0f, kcd->curr.bmface, kcd->curr.cage); + kcd, maxsize * 2.0f, kcd->curr.ob, kcd->curr.base_index, kcd->curr.bmface, kcd->curr.cage); } return density ? min_ff(maxsize / ((float)density * 0.5f), maxsize) : maxsize; } -/* Snap to edge in a specified angle. +/* Snap to edge when in a constrained mode. * Returns 'lambda' calculated (in screen-space). */ -static bool knife_snap_edge_in_angle(KnifeTool_OpData *kcd, - const float sco[3], - const float kfv1_sco[2], - const float kfv2_sco[2], - float *r_dist_sq, - float *r_lambda) -{ - /* if snapping, check we're in bounds */ +static bool knife_snap_edge_constrained(KnifeTool_OpData *kcd, + const float sco[3], + const float kfv1_sco[2], + const float kfv2_sco[2], + float *r_dist_sq, + float *r_lambda) +{ + /* If snapping, check we're in bounds. */ float sco_snap[2]; isect_line_line_v2_point(kfv1_sco, kfv2_sco, kcd->prev.mval, kcd->curr.mval, sco_snap); float lambda = line_point_factor_v2(sco_snap, kfv1_sco, kfv2_sco); - /* be strict about angle-snapping within edge */ + /* Be strict when constrained within edge. */ if ((lambda < 0.0f - KNIFE_FLT_EPSBIG) || (lambda > 1.0f + KNIFE_FLT_EPSBIG)) { return false; } @@ -2309,7 +3319,7 @@ static bool knife_snap_edge_in_angle(KnifeTool_OpData *kcd, return false; } -/* use when lambda is in screen-space */ +/* Use when lambda is in screen-space. */ static void knife_interp_v3_v3v3(const KnifeTool_OpData *kcd, float r_co[3], const float v1[3], @@ -2320,23 +3330,21 @@ static void knife_interp_v3_v3v3(const KnifeTool_OpData *kcd, interp_v3_v3v3(r_co, v1, v2, lambda_ss); } else { - /* transform into screen-space, interp, then transform back */ + /* Transform into screen-space, interp, then transform back. */ float v1_ss[3], v2_ss[3]; - mul_v3_project_m4_v3(v1_ss, (float(*)[4])kcd->projmat, v1); - mul_v3_project_m4_v3(v2_ss, (float(*)[4])kcd->projmat, v2); + mul_v3_project_m4_v3(v1_ss, (float(*)[4])kcd->vc.rv3d->persmat, v1); + mul_v3_project_m4_v3(v2_ss, (float(*)[4])kcd->vc.rv3d->persmat, v2); interp_v3_v3v3(r_co, v1_ss, v2_ss, lambda_ss); - mul_project_m4_v3((float(*)[4])kcd->projmat_inv, r_co); + mul_project_m4_v3((float(*)[4])kcd->vc.rv3d->persinv, r_co); } } -/* p is closest point on edge to the mouse cursor */ -static KnifeEdge *knife_find_closest_edge_of_face(KnifeTool_OpData *kcd, - BMFace *f, - float p[3], - float cagep[3]) +/* p is closest point on edge to the mouse cursor. */ +static KnifeEdge *knife_find_closest_edge_of_face( + KnifeTool_OpData *kcd, Object *ob, uint base_index, BMFace *f, float p[3], float cagep[3]) { float sco[2]; float maxdist; @@ -2361,21 +3369,27 @@ static KnifeEdge *knife_find_closest_edge_of_face(KnifeTool_OpData *kcd, knife_project_v2(kcd, cagep, sco); - /* look through all edges associated with this face */ - list = knife_get_face_kedges(kcd, f); + /* Look through all edges associated with this face. */ + list = knife_get_face_kedges(kcd, ob, base_index, f); for (ref = list->first; ref; ref = ref->next) { KnifeEdge *kfe = ref->ref; float kfv1_sco[2], kfv2_sco[2], test_cagep[3]; float lambda; - /* project edge vertices into screen space */ + if (kfe->is_invalid) { + continue; + } + + /* Project edge vertices into screen space. */ knife_project_v2(kcd, kfe->v1->cageco, kfv1_sco); knife_project_v2(kcd, kfe->v2->cageco, kfv2_sco); - /* check if we're close enough and calculate 'lambda' */ - if (kcd->is_angle_snapping) { + /* Check if we're close enough and calculate 'lambda'. */ + /* In constrained mode calculate lambda differently, unless constrained along kcd->prev.edge */ + if ((kcd->is_angle_snapping || kcd->axis_constrained) && (kfe != kcd->prev.edge) && + (kcd->mode == MODE_DRAGGING)) { dis_sq = curdis_sq; - if (!knife_snap_edge_in_angle(kcd, sco, kfv1_sco, kfv2_sco, &dis_sq, &lambda)) { + if (!knife_snap_edge_constrained(kcd, sco, kfv1_sco, kfv2_sco, &dis_sq, &lambda)) { continue; } } @@ -2389,12 +3403,12 @@ static KnifeEdge *knife_find_closest_edge_of_face(KnifeTool_OpData *kcd, } } - /* now we have 'lambda' calculated (in screen-space) */ + /* Now we have 'lambda' calculated (in screen-space). */ knife_interp_v3_v3v3(kcd, test_cagep, kfe->v1->cageco, kfe->v2->cageco, lambda); if (RV3D_CLIPPING_ENABLED(kcd->vc.v3d, kcd->vc.rv3d)) { - /* check we're in the view */ - if (ED_view3d_clipping_test(kcd->vc.rv3d, test_cagep, true)) { + /* Check we're in the view */ + if (ED_view3d_clipping_test(kcd->vc.rv3d, test_cagep, false)) { continue; } } @@ -2417,16 +3431,18 @@ static KnifeEdge *knife_find_closest_edge_of_face(KnifeTool_OpData *kcd, interp_v3_v3v3(p, cure->v1->co, cure->v2->co, lambda); } - /* update mouse coordinates to the snapped-to edge's screen coordinates - * this is important for angle snap, which uses the previous mouse position */ + /* Update mouse coordinates to the snapped-to edge's screen coordinates + * this is important for angle snap, which uses the previous mouse position. */ edgesnap = new_knife_vert(kcd, p, cagep); + edgesnap->ob = ob; + edgesnap->base_index = base_index; knife_project_v2(kcd, edgesnap->cageco, kcd->curr.mval); } return cure; } -/* find a vertex near the mouse cursor, if it exists */ +/* Find a vertex near the mouse cursor, if it exists. */ static KnifeVert *knife_find_closest_vert_of_edge(KnifeTool_OpData *kcd, KnifeEdge *kfe, float p[3], @@ -2458,9 +3474,9 @@ static KnifeVert *knife_find_closest_vert_of_edge(KnifeTool_OpData *kcd, knife_project_v2(kcd, kfv->cageco, kfv_sco); - /* be strict about angle snapping, the vertex needs to be very close to the angle, - * or we ignore */ - if (kcd->is_angle_snapping) { + /* Be strict when in a constrained mode, the vertex needs to be very close to the cut line, + * or we ignore. */ + if ((kcd->is_angle_snapping || kcd->axis_constrained) && (kcd->mode == MODE_DRAGGING)) { if (dist_squared_to_line_segment_v2(kfv_sco, kcd->prev.mval, kcd->curr.mval) > KNIFE_FLT_EPSBIG) { continue; @@ -2470,7 +3486,7 @@ static KnifeVert *knife_find_closest_vert_of_edge(KnifeTool_OpData *kcd, dis_sq = len_squared_v2v2(kfv_sco, sco); if (dis_sq < curdis_sq && dis_sq < maxdist_sq) { if (!RV3D_CLIPPING_ENABLED(kcd->vc.v3d, kcd->vc.rv3d) || - !ED_view3d_clipping_test(kcd->vc.rv3d, kfv->cageco, true)) { + !ED_view3d_clipping_test(kcd->vc.rv3d, kfv->cageco, false)) { curv = kfv; curdis_sq = dis_sq; copy_v2_v2(cur_kfv_sco, kfv_sco); @@ -2482,8 +3498,8 @@ static KnifeVert *knife_find_closest_vert_of_edge(KnifeTool_OpData *kcd, copy_v3_v3(p, curv->co); copy_v3_v3(cagep, curv->cageco); - /* update mouse coordinates to the snapped-to vertex's screen coordinates - * this is important for angle snap, which uses the previous mouse position */ + /* Update mouse coordinates to the snapped-to vertex's screen coordinates + * this is important for angle snap, which uses the previous mouse position. */ copy_v2_v2(kcd->curr.mval, cur_kfv_sco); } @@ -2510,12 +3526,21 @@ static float snap_v2_angle(float r[2], const float v[2], const float v_ref[2], f return angle + angle_delta; } -/* update both kcd->curr.mval and kcd->mval to snap to required angle */ -static bool knife_snap_angle(KnifeTool_OpData *kcd) +/* Update both kcd->curr.mval and kcd->mval to snap to required angle. */ +static bool knife_snap_angle_screen(KnifeTool_OpData *kcd) { - const float dvec_ref[2] = {0.0f, 1.0f}; + const float dvec_ref[2] = {1.0f, 0.0f}; float dvec[2], dvec_snap[2]; - float snap_step = DEG2RADF(45); + + float snap_step; + /* Currently user can input any float between 0 and 90. */ + if (kcd->angle_snapping_increment > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT && + kcd->angle_snapping_increment < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) { + snap_step = DEG2RADF(kcd->angle_snapping_increment); + } + else { + snap_step = DEG2RADF(KNIFE_DEFAULT_ANGLE_SNAPPING_INCREMENT); + } sub_v2_v2v2(dvec, kcd->curr.mval, kcd->prev.mval); if (is_zero_v2(dvec)) { @@ -2532,6 +3557,273 @@ static bool knife_snap_angle(KnifeTool_OpData *kcd) } /** + * Snaps a 3d vector to an angle, relative to \a v_ref, along the plane with normal \a plane_no. + */ +static float snap_v3_angle_plane( + float r[3], const float v[3], const float v_ref[3], const float plane_no[3], float snap_step) +{ + /* Calculate angle between current cut vector and reference vector. */ + float angle, angle_delta; + angle = angle_signed_on_axis_v3v3_v3(v, v_ref, plane_no); + /* Use this to calculate the angle to rotate by based on snap_step. */ + angle_delta = (roundf(angle / snap_step) * snap_step) - angle; + + /* Snap to angle. */ + rotate_v3_v3v3fl(r, v, plane_no, angle_delta); + return angle + angle_delta; +} + +/* Snap to required angle along the plane of the face nearest to kcd->prev. */ +static bool knife_snap_angle_relative(KnifeTool_OpData *kcd) +{ + Ref *ref; + KnifeEdge *kfe; + KnifeVert *kfv; + BMFace *f; + float refv[3]; + + /* Ray for kcd->curr. */ + float curr_origin[3]; + float curr_origin_ofs[3]; + float curr_ray[3], curr_ray_normal[3]; + float curr_co[3], curr_cage[3]; /* Unused. */ + + float plane[4]; + float ray_hit[3]; + float lambda; + + knife_input_ray_segment(kcd, kcd->curr.mval, 1.0f, curr_origin, curr_origin_ofs); + sub_v3_v3v3(curr_ray, curr_origin_ofs, curr_origin); + normalize_v3_v3(curr_ray_normal, curr_ray); + + BMFace *fcurr = knife_bvh_raycast( + kcd, curr_origin, curr_ray_normal, 0.0f, NULL, curr_co, curr_cage, NULL); + + if (!fcurr) { + return false; + } + + /* Calculate a reference vector using previous cut segment, edge or vertex. + * If none exists then exit. */ + if (kcd->prev.vert) { + int count = 0; + for (ref = kcd->prev.vert->edges.first; ref; ref = ref->next) { + kfe = ((KnifeEdge *)(ref->ref)); + if (kfe->is_invalid) { + continue; + } + if (kfe->e) { + if (!BM_edge_in_face(kfe->e, fcurr)) { + continue; + } + } + if (count == kcd->snap_edge) { + kfv = compare_v3v3(kfe->v1->cageco, kcd->prev.cage, KNIFE_FLT_EPSBIG) ? kfe->v2 : kfe->v1; + sub_v3_v3v3(refv, kfv->cageco, kcd->prev.cage); + kcd->snap_ref_edge = kfe; + break; + } + count++; + } + } + else if (kcd->prev.edge) { + kfv = compare_v3v3(kcd->prev.edge->v1->cageco, kcd->prev.cage, KNIFE_FLT_EPSBIG) ? + kcd->prev.edge->v2 : + kcd->prev.edge->v1; + sub_v3_v3v3(refv, kfv->cageco, kcd->prev.cage); + kcd->snap_ref_edge = kcd->prev.edge; + } + else { + return false; + } + + /* Choose best face for plane. */ + BMFace *fprev = NULL; + if (kcd->prev.vert && kcd->prev.vert->v) { + for (ref = kcd->prev.vert->faces.first; ref; ref = ref->next) { + f = ((BMFace *)(ref->ref)); + if (f == fcurr) { + fprev = f; + } + } + } + else if (kcd->prev.edge) { + for (ref = kcd->prev.edge->faces.first; ref; ref = ref->next) { + f = ((BMFace *)(ref->ref)); + if (f == fcurr) { + fprev = f; + } + } + } + else { + /* Cut segment was started in a face. */ + float prev_origin[3]; + float prev_origin_ofs[3]; + float prev_ray[3], prev_ray_normal[3]; + float prev_co[3], prev_cage[3]; /* Unused. */ + + knife_input_ray_segment(kcd, kcd->prev.mval, 1.0f, prev_origin, prev_origin_ofs); + + sub_v3_v3v3(prev_ray, prev_origin_ofs, prev_origin); + normalize_v3_v3(prev_ray_normal, prev_ray); + + /* kcd->prev.face is usually not set. */ + fprev = knife_bvh_raycast( + kcd, prev_origin, prev_ray_normal, 0.0f, NULL, prev_co, prev_cage, NULL); + } + + if (!fprev || fprev != fcurr) { + return false; + } + + /* Re-calculate current ray in object space. */ + knife_input_ray_segment(kcd, kcd->curr.mval, 1.0f, curr_origin, curr_origin_ofs); + sub_v3_v3v3(curr_ray, curr_origin_ofs, curr_origin); + normalize_v3_v3(curr_ray_normal, curr_ray); + + plane_from_point_normal_v3(plane, kcd->prev.cage, fprev->no); + + if (isect_ray_plane_v3(curr_origin, curr_ray_normal, plane, &lambda, false)) { + madd_v3_v3v3fl(ray_hit, curr_origin, curr_ray_normal, lambda); + + /* Calculate snap step. */ + float snap_step; + if (kcd->angle_snapping_increment > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT && + kcd->angle_snapping_increment < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) { + snap_step = DEG2RADF(kcd->angle_snapping_increment); + } + else { + snap_step = DEG2RADF(KNIFE_DEFAULT_ANGLE_SNAPPING_INCREMENT); + } + + float v1[3]; + float v2[3]; + float rotated_vec[3]; + /* Maybe check for vectors being zero here? */ + sub_v3_v3v3(v1, ray_hit, kcd->prev.cage); + copy_v3_v3(v2, refv); + kcd->angle = snap_v3_angle_plane(rotated_vec, v1, v2, fprev->no, snap_step); + add_v3_v3(rotated_vec, kcd->prev.cage); + + knife_project_v2(kcd, rotated_vec, kcd->curr.mval); + copy_v2_v2(kcd->mval, kcd->curr.mval); + return true; + } + return false; +} + +static int knife_calculate_snap_ref_edges(KnifeTool_OpData *kcd) +{ + Ref *ref; + KnifeEdge *kfe; + + /* Ray for kcd->curr. */ + float curr_origin[3]; + float curr_origin_ofs[3]; + float curr_ray[3], curr_ray_normal[3]; + float curr_co[3], curr_cage[3]; /* Unused. */ + + knife_input_ray_segment(kcd, kcd->curr.mval, 1.0f, curr_origin, curr_origin_ofs); + sub_v3_v3v3(curr_ray, curr_origin_ofs, curr_origin); + normalize_v3_v3(curr_ray_normal, curr_ray); + + BMFace *fcurr = knife_bvh_raycast( + kcd, curr_origin, curr_ray_normal, 0.0f, NULL, curr_co, curr_cage, NULL); + + int count = 0; + + if (!fcurr) { + return count; + } + + if (kcd->prev.vert) { + for (ref = kcd->prev.vert->edges.first; ref; ref = ref->next) { + kfe = ((KnifeEdge *)(ref->ref)); + if (kfe->is_invalid) { + continue; + } + if (kfe->e) { + if (!BM_edge_in_face(kfe->e, fcurr)) { + continue; + } + } + count++; + } + } + else if (kcd->prev.edge) { + return 1; + } + return count; +} + +/* Reset the snapping angle num input. */ +static void knife_reset_snap_angle_input(KnifeTool_OpData *kcd) +{ + kcd->num.val[0] = 0; + while (kcd->num.str_cur > 0) { + kcd->num.str[kcd->num.str_cur - 1] = '\0'; + kcd->num.str_cur--; + } +} +/** + * Constrains the current cut to an axis. + * If scene orientation is set to anything other than global it takes priority. + * Otherwise kcd->constrain_axis_mode is used. + */ +static void knife_constrain_axis(bContext *C, KnifeTool_OpData *kcd) +{ + /* Obtain current mouse position in world space. */ + float curr_cage_adjusted[3]; + ED_view3d_win_to_3d( + kcd->vc.v3d, kcd->region, kcd->prev.cage, kcd->curr.mval, curr_cage_adjusted); + + /* Constrain axes. */ + Scene *scene = kcd->scene; + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *obedit = (kcd->prev.ob) ? kcd->prev.ob : kcd->vc.obedit; + RegionView3D *rv3d = kcd->region->regiondata; + const short scene_orientation = BKE_scene_orientation_get_index(scene, SCE_ORIENT_DEFAULT); + /* Scene orientation takes priority. */ + const short orientation_type = scene_orientation ? scene_orientation : + kcd->constrain_axis_mode - 1; + const int pivot_point = scene->toolsettings->transform_pivot_point; + float mat[3][3]; + ED_transform_calc_orientation_from_type_ex( + scene, view_layer, kcd->vc.v3d, rv3d, obedit, obedit, orientation_type, pivot_point, mat); + + /* Apply orientation matrix (can be simplified?). */ + float co[3][3]; + copy_v3_v3(co[0], kcd->prev.cage); + copy_v3_v3(co[2], curr_cage_adjusted); + invert_m3(mat); + mul_m3_m3_pre(co, mat); + for (int i = 0; i <= 2; i++) { + if ((kcd->constrain_axis - 1) != i) { + /* kcd->curr_cage_adjusted[i] = prev_cage_adjusted[i]; */ + co[2][i] = co[0][i]; + } + } + invert_m3(mat); + mul_m3_m3_pre(co, mat); + copy_v3_v3(kcd->prev.cage, co[0]); + copy_v3_v3(curr_cage_adjusted, co[2]); + + /* Set mval to closest point on constrained line in screen space. */ + float curr_screenspace[2]; + float prev_screenspace[2]; + knife_project_v2(kcd, curr_cage_adjusted, curr_screenspace); + knife_project_v2(kcd, kcd->prev.cage, prev_screenspace); + float intersection[2]; + if (closest_to_line_v2(intersection, kcd->curr.mval, prev_screenspace, curr_screenspace)) { + copy_v2_v2(kcd->curr.mval, intersection); + } + else { + copy_v2_v2(kcd->curr.mval, curr_screenspace); + } + copy_v2_v2(kcd->mval, kcd->curr.mval); +} + +/** * \return true when `kcd->curr.co` & `kcd->curr.cage` are set. * * In this case `is_space` is nearly always false. @@ -2539,7 +3831,7 @@ static bool knife_snap_angle(KnifeTool_OpData *kcd) * In this case the selection-buffer is used to select the face, * then the closest `vert` or `edge` is set, and those will enable `is_co_set`. */ -static bool knife_snap_update_from_mval(KnifeTool_OpData *kcd, const float mval[2]) +static bool knife_snap_update_from_mval(bContext *C, KnifeTool_OpData *kcd, const float mval[2]) { knife_pos_data_clear(&kcd->curr); copy_v2_v2(kcd->curr.mval, mval); @@ -2547,20 +3839,37 @@ static bool knife_snap_update_from_mval(KnifeTool_OpData *kcd, const float mval[ /* view matrix may have changed, reproject */ knife_project_v2(kcd, kcd->prev.cage, kcd->prev.mval); - if (kcd->angle_snapping && (kcd->mode == MODE_DRAGGING)) { - kcd->is_angle_snapping = knife_snap_angle(kcd); - } - else { - kcd->is_angle_snapping = false; + kcd->is_angle_snapping = false; + if (kcd->mode == MODE_DRAGGING) { + if (kcd->angle_snapping) { + if (kcd->angle_snapping_mode == KNF_CONSTRAIN_ANGLE_MODE_SCREEN) { + kcd->is_angle_snapping = knife_snap_angle_screen(kcd); + } + else if (kcd->angle_snapping_mode == KNF_CONSTRAIN_ANGLE_MODE_RELATIVE) { + kcd->is_angle_snapping = knife_snap_angle_relative(kcd); + if (kcd->is_angle_snapping) { + kcd->snap_ref_edges_count = knife_calculate_snap_ref_edges(kcd); + } + } + } + + if (kcd->axis_constrained) { + knife_constrain_axis(C, kcd); + } } { - kcd->curr.bmface = knife_find_closest_face( - kcd, kcd->curr.co, kcd->curr.cage, &kcd->curr.is_space); + kcd->curr.ob = kcd->vc.obedit; + kcd->curr.bmface = knife_find_closest_face(kcd, + &kcd->curr.ob, + &kcd->curr.base_index, + &kcd->curr.is_space, + kcd->curr.co, + kcd->curr.cage); if (kcd->curr.bmface) { kcd->curr.edge = knife_find_closest_edge_of_face( - kcd, kcd->curr.bmface, kcd->curr.co, kcd->curr.cage); + kcd, kcd->curr.ob, kcd->curr.base_index, kcd->curr.bmface, kcd->curr.co, kcd->curr.cage); } if (kcd->curr.edge) { @@ -2576,86 +3885,182 @@ static bool knife_snap_update_from_mval(KnifeTool_OpData *kcd, const float mval[ return kcd->curr.vert || kcd->curr.edge || (kcd->curr.bmface && !kcd->curr.is_space); } +/** + * TODO: Undo currently assumes that the most recent cut segment added is + * the last valid KnifeEdge in the kcd->kedges mempool. This could break in + * the future so it may be better to store the KnifeEdges for each KnifeUndoFrame + * on a stack. This stack could then be used instead of iterating over the mempool. + */ +static void knifetool_undo(KnifeTool_OpData *kcd) +{ + Ref *ref; + KnifeEdge *kfe, *newkfe; + KnifeEdge *lastkfe = NULL; + KnifeVert *v1, *v2; + KnifeUndoFrame *undo; + BLI_mempool_iter iterkfe; + + if (!BLI_stack_is_empty(kcd->undostack)) { + undo = BLI_stack_peek(kcd->undostack); + + /* Undo edge splitting. */ + for (int i = 0; i < undo->splits; i++) { + BLI_stack_pop(kcd->splitstack, &newkfe); + BLI_stack_pop(kcd->splitstack, &kfe); + knife_join_edge(newkfe, kfe); + } + + for (int i = 0; i < undo->cuts; i++) { + + BLI_mempool_iternew(kcd->kedges, &iterkfe); + for (kfe = BLI_mempool_iterstep(&iterkfe); kfe; kfe = BLI_mempool_iterstep(&iterkfe)) { + if (!kfe->is_cut || kfe->is_invalid || kfe->splits) { + continue; + } + lastkfe = kfe; + } + + if (lastkfe) { + lastkfe->is_invalid = true; + + /* TODO: Are they always guaranteed to be in this order? */ + v1 = lastkfe->v1; + v2 = lastkfe->v2; + + /* Only remove first vertex if it is the start segment of the cut. */ + if (!v1->is_invalid && !v1->is_splitting) { + v1->is_invalid = true; + /* If the first vertex is touching any other cut edges don't remove it. */ + for (ref = v1->edges.first; ref; ref = ref->next) { + kfe = ref->ref; + if (kfe->is_cut && !kfe->is_invalid) { + v1->is_invalid = false; + break; + } + } + } + + /* Only remove second vertex if it is the end segment of the cut. */ + if (!v2->is_invalid && !v2->is_splitting) { + v2->is_invalid = true; + /* If the second vertex is touching any other cut edges don't remove it. */ + for (ref = v2->edges.first; ref; ref = ref->next) { + kfe = ref->ref; + if (kfe->is_cut && !kfe->is_invalid) { + v2->is_invalid = false; + break; + } + } + } + } + } + + if (kcd->mode == MODE_DRAGGING) { + /* Restore kcd->prev. */ + kcd->prev = undo->pos; + } + + /* Restore data for distance and angle measurements. */ + kcd->mdata = undo->mdata; + + BLI_stack_discard(kcd->undostack); + } +} + /** \} */ /* -------------------------------------------------------------------- */ /** \name #KnifeTool_OpData (#op->customdata) Init and Free * \{ */ -static void knifetool_init_bmbvh(KnifeTool_OpData *kcd) +static void knifetool_init_cagecos(KnifeTool_OpData *kcd, Object *ob, uint base_index) { - BM_mesh_elem_index_ensure(kcd->em->bm, BM_VERT); Scene *scene_eval = (Scene *)DEG_get_evaluated_id(kcd->vc.depsgraph, &kcd->scene->id); - Object *obedit_eval = (Object *)DEG_get_evaluated_id(kcd->vc.depsgraph, &kcd->ob->id); + Object *obedit_eval = (Object *)DEG_get_evaluated_id(kcd->vc.depsgraph, &ob->id); BMEditMesh *em_eval = BKE_editmesh_from_object(obedit_eval); - kcd->cagecos = (const float(*)[3])BKE_editmesh_vert_coords_alloc( - kcd->vc.depsgraph, em_eval, scene_eval, obedit_eval, NULL); + BM_mesh_elem_index_ensure(em_eval->bm, BM_VERT); - kcd->bmbvh = BKE_bmbvh_new_from_editmesh( - kcd->em, - BMBVH_RETURN_ORIG | - ((kcd->only_select && kcd->cut_through) ? BMBVH_RESPECT_SELECT : BMBVH_RESPECT_HIDDEN), - kcd->cagecos, - false); + kcd->cagecos[base_index] = (const float(*)[3])BKE_editmesh_vert_coords_alloc( + kcd->vc.depsgraph, em_eval, scene_eval, obedit_eval, NULL); } -static void knifetool_free_bmbvh(KnifeTool_OpData *kcd) +static void knifetool_free_cagecos(KnifeTool_OpData *kcd, uint base_index) { - if (kcd->bmbvh) { - BKE_bmbvh_free(kcd->bmbvh); - kcd->bmbvh = NULL; - } - - if (kcd->cagecos) { - MEM_freeN((void *)kcd->cagecos); - kcd->cagecos = NULL; + if (kcd->cagecos[base_index]) { + MEM_freeN((void *)kcd->cagecos[base_index]); + kcd->cagecos[base_index] = NULL; } } static void knife_init_colors(KnifeColors *colors) { - /* possible BMESH_TODO: add explicit themes or calculate these by + /* Possible BMESH_TODO: add explicit themes or calculate these by * figuring out contrasting colors with grid / edges / verts - * a la UI_make_axis_color */ + * a la UI_make_axis_color. */ UI_GetThemeColorType3ubv(TH_NURB_VLINE, SPACE_VIEW3D, colors->line); UI_GetThemeColorType3ubv(TH_NURB_ULINE, SPACE_VIEW3D, colors->edge); + UI_GetThemeColorType3ubv(TH_NURB_SEL_ULINE, SPACE_VIEW3D, colors->edge_extra); UI_GetThemeColorType3ubv(TH_HANDLE_SEL_VECT, SPACE_VIEW3D, colors->curpoint); UI_GetThemeColorType3ubv(TH_HANDLE_SEL_VECT, SPACE_VIEW3D, colors->curpoint_a); colors->curpoint_a[3] = 102; UI_GetThemeColorType3ubv(TH_ACTIVE_SPLINE, SPACE_VIEW3D, colors->point); UI_GetThemeColorType3ubv(TH_ACTIVE_SPLINE, SPACE_VIEW3D, colors->point_a); colors->point_a[3] = 102; + + UI_GetThemeColorType3ubv(TH_AXIS_X, SPACE_VIEW3D, colors->xaxis); + UI_GetThemeColorType3ubv(TH_AXIS_Y, SPACE_VIEW3D, colors->yaxis); + UI_GetThemeColorType3ubv(TH_AXIS_Z, SPACE_VIEW3D, colors->zaxis); + UI_GetThemeColorType3ubv(TH_TRANSFORM, SPACE_VIEW3D, colors->axis_extra); } /* called when modal loop selection gets set up... */ -static void knifetool_init(ViewContext *vc, +static void knifetool_init(bContext *C, + ViewContext *vc, KnifeTool_OpData *kcd, const bool only_select, const bool cut_through, + const bool xray, + const int visible_measurements, + const int angle_snapping, + const float angle_snapping_increment, const bool is_interactive) { kcd->vc = *vc; Scene *scene = vc->scene; - Object *obedit = vc->obedit; - /* assign the drawing handle for drawing preview line... */ + /* Assign the drawing handle for drawing preview line... */ kcd->scene = scene; - kcd->ob = obedit; kcd->region = vc->region; - invert_m4_m4_safe_ortho(kcd->ob_imat, kcd->ob->obmat); + kcd->objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + CTX_data_view_layer(C), CTX_wm_view3d(C), &kcd->objects_len); + + Object *ob; + BMEditMesh *em; + kcd->cagecos = MEM_callocN(sizeof(*kcd->cagecos) * kcd->objects_len, "knife cagecos"); + for (uint b = 0; b < kcd->objects_len; b++) { + ob = kcd->objects[b]; + em = BKE_editmesh_from_object(ob); + knifetool_init_cagecos(kcd, ob, b); - kcd->em = BKE_editmesh_from_object(kcd->ob); + /* Can't usefully select resulting edges in face mode. */ + kcd->select_result = (em->selectmode != SCE_SELECT_FACE); + } + knife_bvh_init(kcd); - /* cut all the way through the mesh if use_occlude_geometry button not pushed */ + /* Cut all the way through the mesh if use_occlude_geometry button not pushed. */ kcd->is_interactive = is_interactive; kcd->cut_through = cut_through; kcd->only_select = only_select; - - knifetool_init_bmbvh(kcd); + kcd->depth_test = xray; + kcd->dist_angle_mode = visible_measurements; + kcd->show_dist_angle = (kcd->dist_angle_mode != KNF_MEASUREMENT_NONE); + kcd->angle_snapping_mode = angle_snapping; + kcd->angle_snapping = (kcd->angle_snapping_mode != KNF_CONSTRAIN_ANGLE_MODE_NONE); + kcd->angle_snapping_increment = angle_snapping_increment; kcd->arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 15), "knife"); #ifdef USE_NET_ISLAND_CONNECT @@ -2666,7 +4071,7 @@ static void knifetool_init(ViewContext *vc, kcd->vthresh = KMAXDIST - 1; kcd->ethresh = KMAXDIST; - knife_recalc_projmat(kcd); + knife_recalc_ortho(kcd); ED_region_tag_redraw(kcd->region); @@ -2674,14 +4079,14 @@ static void knifetool_init(ViewContext *vc, kcd->kverts = BLI_mempool_create(sizeof(KnifeVert), 0, 512, BLI_MEMPOOL_ALLOW_ITER); kcd->kedges = BLI_mempool_create(sizeof(KnifeEdge), 0, 512, BLI_MEMPOOL_ALLOW_ITER); + kcd->undostack = BLI_stack_new(sizeof(KnifeUndoFrame), "knife undostack"); + kcd->splitstack = BLI_stack_new(sizeof(KnifeEdge *), "knife splitstack"); + kcd->origedgemap = BLI_ghash_ptr_new("knife origedgemap"); kcd->origvertmap = BLI_ghash_ptr_new("knife origvertmap"); kcd->kedgefacemap = BLI_ghash_ptr_new("knife kedgefacemap"); kcd->facetrimap = BLI_ghash_ptr_new("knife facetrimap"); - /* can't usefully select resulting edges in face mode */ - kcd->select_result = (kcd->em->selectmode != SCE_SELECT_FACE); - knife_pos_data_clear(&kcd->curr); knife_pos_data_clear(&kcd->prev); @@ -2691,6 +4096,16 @@ static void knifetool_init(ViewContext *vc, knife_init_colors(&kcd->colors); } + + kcd->axis_string[0] = ' '; + kcd->axis_string[1] = '\0'; + + /* Initialise num input handling for angle snapping. */ + initNumInput(&kcd->num); + kcd->num.idx_max = 0; + kcd->num.val_flag[0] |= NUM_NO_NEGATIVE; + kcd->num.unit_sys = scene->unit.system; + kcd->num.unit_type[0] = B_UNIT_NONE; } /* called when modal loop selection is done... */ @@ -2703,15 +4118,18 @@ static void knifetool_exit_ex(KnifeTool_OpData *kcd) if (kcd->is_interactive) { WM_cursor_modal_restore(kcd->vc.win); - /* deactivate the extra drawing stuff in 3D-View */ + /* Deactivate the extra drawing stuff in 3D-View. */ ED_region_draw_cb_exit(kcd->region->type, kcd->draw_handle); } - /* free the custom data */ + /* Free the custom data. */ BLI_mempool_destroy(kcd->refs); BLI_mempool_destroy(kcd->kverts); BLI_mempool_destroy(kcd->kedges); + BLI_stack_free(kcd->undostack); + BLI_stack_free(kcd->splitstack); + BLI_ghash_free(kcd->origedgemap, NULL, NULL); BLI_ghash_free(kcd->origvertmap, NULL, NULL); BLI_ghash_free(kcd->kedgefacemap, NULL, NULL); @@ -2723,18 +4141,28 @@ static void knifetool_exit_ex(KnifeTool_OpData *kcd) #endif BLI_gset_free(kcd->edgenet.edge_visit, NULL); - /* tag for redraw */ + /* Tag for redraw. */ ED_region_tag_redraw(kcd->region); - knifetool_free_bmbvh(kcd); + /* Knife BVH cleanup. */ + for (int i = 0; i < kcd->objects_len; i++) { + knifetool_free_cagecos(kcd, i); + } + MEM_freeN(kcd->cagecos); + knife_bvh_free(kcd); + /* Linehits cleanup. */ if (kcd->linehits) { MEM_freeN(kcd->linehits); } - /* destroy kcd itself */ + /* Free object bases. */ + MEM_freeN(kcd->objects); + + /* Destroy kcd itself. */ MEM_freeN(kcd); } + static void knifetool_exit(wmOperator *op) { KnifeTool_OpData *kcd = op->customdata; @@ -2748,24 +4176,24 @@ static void knifetool_exit(wmOperator *op) /** \name Mouse-Moving Event Updates * \{ */ -/* update active knife edge/vert pointers */ -static int knife_update_active(KnifeTool_OpData *kcd) +/* Update active knife edge/vert pointers. */ +static int knife_update_active(bContext *C, KnifeTool_OpData *kcd) { - /* if no hits are found this would normally default to (0, 0, 0) so instead + /* If no hits are found this would normally default to (0, 0, 0) so instead * get a point at the mouse ray closest to the previous point. * Note that drawing lines in `free-space` isn't properly supported * but there's no guarantee (0, 0, 0) has any geometry either - campbell */ - if (!knife_snap_update_from_mval(kcd, kcd->mval)) { + if (!knife_snap_update_from_mval(C, kcd, kcd->mval)) { float origin[3]; float origin_ofs[3]; knife_input_ray_segment(kcd, kcd->curr.mval, 1.0f, origin, origin_ofs); if (!isect_line_plane_v3( - kcd->curr.cage, origin, origin_ofs, kcd->prev.cage, kcd->proj_zaxis)) { + kcd->curr.cage, origin, origin_ofs, kcd->prev.cage, kcd->vc.rv3d->viewinv[2])) { copy_v3_v3(kcd->curr.cage, kcd->prev.cage); - /* should never fail! */ + /* Should never fail! */ BLI_assert(0); } } @@ -2776,20 +4204,20 @@ static int knife_update_active(KnifeTool_OpData *kcd) return 1; } -static void knifetool_update_mval(KnifeTool_OpData *kcd, const float mval[2]) +static void knifetool_update_mval(bContext *C, KnifeTool_OpData *kcd, const float mval[2]) { - knife_recalc_projmat(kcd); + knife_recalc_ortho(kcd); copy_v2_v2(kcd->mval, mval); - if (knife_update_active(kcd)) { + if (knife_update_active(C, kcd)) { ED_region_tag_redraw(kcd->region); } } -static void knifetool_update_mval_i(KnifeTool_OpData *kcd, const int mval_i[2]) +static void knifetool_update_mval_i(bContext *C, KnifeTool_OpData *kcd, const int mval_i[2]) { const float mval[2] = {UNPACK2(mval_i)}; - knifetool_update_mval(kcd, mval); + knifetool_update_mval(C, kcd, mval); } /** \} */ @@ -2798,21 +4226,40 @@ static void knifetool_update_mval_i(KnifeTool_OpData *kcd, const int mval_i[2]) /** \name Finalization * \{ */ -/* called on tool confirmation */ +/* Called on tool confirmation. */ static void knifetool_finish_ex(KnifeTool_OpData *kcd) { - knife_make_cuts(kcd); + Object *ob; + BMEditMesh *em; + for (uint b = 0; b < kcd->objects_len; b++) { + ob = kcd->objects[b]; + em = BKE_editmesh_from_object(ob); + + knife_make_cuts(kcd, ob); + + EDBM_selectmode_flush(em); + EDBM_update(ob->data, + &(const struct EDBMUpdate_Params){ + .calc_looptri = true, + .calc_normals = true, + .is_destructive = true, + }); + } +} + +static void knifetool_finish_single_ex(KnifeTool_OpData *kcd, Object *ob, uint UNUSED(base_index)) +{ + knife_make_cuts(kcd, ob); + + BMEditMesh *em = BKE_editmesh_from_object(ob); - EDBM_selectmode_flush(kcd->em); - EDBM_update(kcd->ob->data, + EDBM_selectmode_flush(em); + EDBM_update(ob->data, &(const struct EDBMUpdate_Params){ .calc_looptri = true, .calc_normals = true, .is_destructive = true, }); - - /* Re-tessellating makes this invalid, don't use again by accident. */ - knifetool_free_bmbvh(kcd); } static void knifetool_finish(wmOperator *op) @@ -2838,12 +4285,24 @@ wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf) static const EnumPropertyItem modal_items[] = { {KNF_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, {KNF_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""}, + {KNF_MODAL_UNDO, "UNDO", 0, "Undo", ""}, {KNF_MODAL_MIDPOINT_ON, "SNAP_MIDPOINTS_ON", 0, "Snap to Midpoints On", ""}, {KNF_MODAL_MIDPOINT_OFF, "SNAP_MIDPOINTS_OFF", 0, "Snap to Midpoints Off", ""}, {KNF_MODAL_IGNORE_SNAP_ON, "IGNORE_SNAP_ON", 0, "Ignore Snapping On", ""}, {KNF_MODAL_IGNORE_SNAP_OFF, "IGNORE_SNAP_OFF", 0, "Ignore Snapping Off", ""}, {KNF_MODAL_ANGLE_SNAP_TOGGLE, "ANGLE_SNAP_TOGGLE", 0, "Toggle Angle Snapping", ""}, + {KNF_MODAL_CYCLE_ANGLE_SNAP_EDGE, + "CYCLE_ANGLE_SNAP_EDGE", + 0, + "Cycle Angle Snapping Relative Edge", + ""}, {KNF_MODAL_CUT_THROUGH_TOGGLE, "CUT_THROUGH_TOGGLE", 0, "Toggle Cut Through", ""}, + {KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE, + "SHOW_DISTANCE_ANGLE_TOGGLE", + 0, + "Toggle Distance and Angle Measurements", + ""}, + {KNF_MODAL_DEPTH_TEST_TOGGLE, "DEPTH_TEST_TOGGLE", 0, "Toggle Depth Testing", ""}, {KNF_MODAL_NEW_CUT, "NEW_CUT", 0, "End Current Cut", ""}, {KNF_MODAL_ADD_CUT, "ADD_CUT", 0, "Add Cut", ""}, {KNF_MODAL_ADD_CUT_CLOSED, "ADD_CUT_CLOSED", 0, "Add Cut Closed", ""}, @@ -2853,7 +4312,7 @@ wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Knife Tool Modal Map"); - /* this function is called for each spacetype, only needs to add map once */ + /* This function is called for each spacetype, only needs to add map once. */ if (keymap && keymap->modal_items) { return NULL; } @@ -2865,28 +4324,66 @@ wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf) return keymap; } +/* Turn off angle snapping. */ +static void knifetool_disable_angle_snapping(KnifeTool_OpData *kcd) +{ + kcd->angle_snapping_mode = KNF_CONSTRAIN_ANGLE_MODE_NONE; + kcd->angle_snapping = false; + kcd->is_angle_snapping = false; +} + +/* Turn off orientation locking. */ +static void knifetool_disable_orientation_locking(KnifeTool_OpData *kcd) +{ + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_MODE_NONE; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_NONE; + kcd->axis_constrained = false; +} + static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) { - Object *obedit = CTX_data_edit_object(C); KnifeTool_OpData *kcd = op->customdata; bool do_refresh = false; - if (!obedit || obedit->type != OB_MESH || BKE_editmesh_from_object(obedit) != kcd->em) { + if (!kcd->curr.ob || kcd->curr.ob->type != OB_MESH) { knifetool_exit(op); ED_workspace_status_text(C, NULL); return OPERATOR_FINISHED; } - em_setup_viewcontext(C, &kcd->vc); kcd->region = kcd->vc.region; - ED_view3d_init_mats_rv3d(obedit, kcd->vc.rv3d); /* needed to initialize clipping */ + ED_view3d_init_mats_rv3d(kcd->curr.ob, kcd->vc.rv3d); /* Needed to initialize clipping. */ if (kcd->mode == MODE_PANNING) { kcd->mode = kcd->prevmode; } - /* handle modal keymap */ + bool handled = false; + float snapping_increment_temp; + + if (kcd->angle_snapping) { + if (kcd->num.str_cur >= 2) { + knife_reset_snap_angle_input(kcd); + } + knife_update_header(C, op, kcd); /* Update the angle multiple. */ + /* Modal numinput active, try to handle numeric inputs first... */ + if (event->val == KM_PRESS && hasNumInput(&kcd->num) && handleNumInput(C, &kcd->num, event)) { + handled = true; + applyNumInput(&kcd->num, &snapping_increment_temp); + /* Restrict number key input to 0 - 90 degree range. */ + if (snapping_increment_temp > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT && + snapping_increment_temp < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) { + kcd->angle_snapping_increment = snapping_increment_temp; + } + knife_update_active(C, kcd); + knife_update_header(C, op, kcd); + ED_region_tag_redraw(kcd->region); + return OPERATOR_RUNNING_MODAL; + } + } + + /* Handle modal keymap. */ if (event->type == EVT_MODAL_MAP) { switch (event->val) { case KNF_MODAL_CANCEL: @@ -2906,55 +4403,111 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) ED_workspace_status_text(C, NULL); return OPERATOR_FINISHED; + case KNF_MODAL_UNDO: + knifetool_undo(kcd); + knife_update_active(C, kcd); + ED_region_tag_redraw(kcd->region); + handled = true; + break; case KNF_MODAL_MIDPOINT_ON: kcd->snap_midpoints = true; - knife_recalc_projmat(kcd); - knife_update_active(kcd); + knife_recalc_ortho(kcd); + knife_update_active(C, kcd); knife_update_header(C, op, kcd); ED_region_tag_redraw(kcd->region); do_refresh = true; + handled = true; break; case KNF_MODAL_MIDPOINT_OFF: kcd->snap_midpoints = false; - knife_recalc_projmat(kcd); - knife_update_active(kcd); + knife_recalc_ortho(kcd); + knife_update_active(C, kcd); knife_update_header(C, op, kcd); ED_region_tag_redraw(kcd->region); do_refresh = true; + handled = true; break; case KNF_MODAL_IGNORE_SNAP_ON: ED_region_tag_redraw(kcd->region); kcd->ignore_vert_snapping = kcd->ignore_edge_snapping = true; knife_update_header(C, op, kcd); do_refresh = true; + handled = true; break; case KNF_MODAL_IGNORE_SNAP_OFF: ED_region_tag_redraw(kcd->region); kcd->ignore_vert_snapping = kcd->ignore_edge_snapping = false; knife_update_header(C, op, kcd); do_refresh = true; + handled = true; break; case KNF_MODAL_ANGLE_SNAP_TOGGLE: - kcd->angle_snapping = !kcd->angle_snapping; + if (kcd->angle_snapping_mode != KNF_CONSTRAIN_ANGLE_MODE_RELATIVE) { + kcd->angle_snapping_mode++; + kcd->snap_ref_edges_count = 0; + kcd->snap_edge = 0; + } + else { + kcd->angle_snapping_mode = KNF_CONSTRAIN_ANGLE_MODE_NONE; + } + kcd->angle_snapping = (kcd->angle_snapping_mode != KNF_CONSTRAIN_ANGLE_MODE_NONE); + kcd->angle_snapping_increment = RAD2DEGF( + RNA_float_get(op->ptr, "angle_snapping_increment")); + knifetool_disable_orientation_locking(kcd); + knife_reset_snap_angle_input(kcd); + knife_update_active(C, kcd); knife_update_header(C, op, kcd); + ED_region_tag_redraw(kcd->region); + do_refresh = true; + handled = true; + break; + case KNF_MODAL_CYCLE_ANGLE_SNAP_EDGE: + if (kcd->angle_snapping && kcd->angle_snapping_mode == KNF_CONSTRAIN_ANGLE_MODE_RELATIVE) { + if (kcd->snap_ref_edges_count) { + kcd->snap_edge++; + kcd->snap_edge %= kcd->snap_ref_edges_count; + } + } do_refresh = true; + handled = true; break; case KNF_MODAL_CUT_THROUGH_TOGGLE: kcd->cut_through = !kcd->cut_through; knife_update_header(C, op, kcd); do_refresh = true; + handled = true; + break; + case KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE: + if (kcd->dist_angle_mode != KNF_MEASUREMENT_ANGLE) { + kcd->dist_angle_mode++; + } + else { + kcd->dist_angle_mode = KNF_MEASUREMENT_NONE; + } + kcd->show_dist_angle = (kcd->dist_angle_mode != KNF_MEASUREMENT_NONE); + knife_update_header(C, op, kcd); + do_refresh = true; + handled = true; + break; + case KNF_MODAL_DEPTH_TEST_TOGGLE: + kcd->depth_test = !kcd->depth_test; + ED_region_tag_redraw(kcd->region); + knife_update_header(C, op, kcd); + do_refresh = true; + handled = true; break; case KNF_MODAL_NEW_CUT: ED_region_tag_redraw(kcd->region); knife_finish_cut(kcd); kcd->mode = MODE_IDLE; + handled = true; break; case KNF_MODAL_ADD_CUT: - knife_recalc_projmat(kcd); + knife_recalc_ortho(kcd); - /* get the value of the event which triggered this one */ + /* Get the value of the event which triggered this one. */ if (event->prevval != KM_RELEASE) { if (kcd->mode == MODE_DRAGGING) { knife_add_cut(kcd); @@ -2965,7 +4518,17 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) kcd->init = kcd->curr; } - /* freehand drawing is incompatible with cut-through */ + /* Preserve correct prev.cage for angle drawing calculations. */ + if (kcd->prev.edge == NULL && kcd->prev.vert == NULL) { + /* "knife_start_cut" moves prev.cage so needs to be recalculated. */ + /* Only occurs if prev was started on a face. */ + knifetool_recast_cageco(kcd, kcd->prev.mval, kcd->mdata.corr_prev_cage); + } + else { + copy_v3_v3(kcd->mdata.corr_prev_cage, kcd->prev.cage); + } + + /* Freehand drawing is incompatible with cut-through. */ if (kcd->cut_through == false) { kcd->is_drag_hold = true; /* No edge snapping while dragging (edges are too sticky when cuts are immediate). */ @@ -2975,12 +4538,14 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) else { kcd->is_drag_hold = false; kcd->ignore_edge_snapping = false; + kcd->is_drag_undo = false; - /* needed because the last face 'hit' is ignored when dragging */ - knifetool_update_mval(kcd, kcd->curr.mval); + /* Needed because the last face 'hit' is ignored when dragging. */ + knifetool_update_mval(C, kcd, kcd->curr.mval); } ED_region_tag_redraw(kcd->region); + handled = true; break; case KNF_MODAL_ADD_CUT_CLOSED: if (kcd->mode == MODE_DRAGGING) { @@ -2988,14 +4553,15 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Shouldn't be possible with default key-layout, just in case. */ if (kcd->is_drag_hold) { kcd->is_drag_hold = false; - knifetool_update_mval(kcd, kcd->curr.mval); + kcd->is_drag_undo = false; + knifetool_update_mval(C, kcd, kcd->curr.mval); } kcd->prev = kcd->curr; kcd->curr = kcd->init; knife_project_v2(kcd, kcd->curr.cage, kcd->curr.mval); - knifetool_update_mval(kcd, kcd->curr.mval); + knifetool_update_mval(C, kcd, kcd->curr.mval); knife_add_cut(kcd); @@ -3003,6 +4569,7 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) knife_finish_cut(kcd); kcd->mode = MODE_IDLE; } + handled = true; break; case KNF_MODAL_PANNING: if (event->val != KM_RELEASE) { @@ -3028,9 +4595,9 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) case WHEELDOWNMOUSE: case NDOF_MOTION: return OPERATOR_PASS_THROUGH; - case MOUSEMOVE: /* mouse moved somewhere to select another loop */ + case MOUSEMOVE: /* Mouse moved somewhere to select another loop. */ if (kcd->mode != MODE_PANNING) { - knifetool_update_mval_i(kcd, event->mval); + knifetool_update_mval_i(C, kcd, event->mval); if (kcd->is_drag_hold) { if (kcd->totlinehit >= 2) { @@ -3043,6 +4610,58 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) } } + if (kcd->angle_snapping) { + if (kcd->num.str_cur >= 2) { + knife_reset_snap_angle_input(kcd); + } + /* Modal numinput inactive, try to handle numeric inputs last... */ + if (!handled && event->val == KM_PRESS && handleNumInput(C, &kcd->num, event)) { + applyNumInput(&kcd->num, &snapping_increment_temp); + /* Restrict number key input to 0 - 90 degree range. */ + if (snapping_increment_temp > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT && + snapping_increment_temp < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) { + kcd->angle_snapping_increment = snapping_increment_temp; + } + knife_update_active(C, kcd); + knife_update_header(C, op, kcd); + ED_region_tag_redraw(kcd->region); + return OPERATOR_RUNNING_MODAL; + } + } + + /* Constrain axes with X,Y,Z keys. */ + if (event->val == KM_PRESS && ELEM(event->type, EVT_XKEY, EVT_YKEY, EVT_ZKEY)) { + if (event->type == EVT_XKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_X) { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_X; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; + kcd->axis_string[0] = 'X'; + } + else if (event->type == EVT_YKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Y) { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Y; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; + kcd->axis_string[0] = 'Y'; + } + else if (event->type == EVT_ZKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Z) { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Z; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; + kcd->axis_string[0] = 'Z'; + } + else { + /* Cycle through modes with repeated key presses. */ + if (kcd->constrain_axis_mode != KNF_CONSTRAIN_AXIS_MODE_LOCAL) { + kcd->constrain_axis_mode++; + kcd->axis_string[0] += 32; /* Lower case. */ + } + else { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_NONE; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_NONE; + } + } + kcd->axis_constrained = (kcd->constrain_axis != KNF_CONSTRAIN_AXIS_NONE); + knifetool_disable_angle_snapping(kcd); + knife_update_header(C, op, kcd); + } + if (kcd->mode == MODE_DRAGGING) { op->flag &= ~OP_IS_MODAL_CURSOR_REGION; } @@ -3051,12 +4670,12 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) } if (do_refresh) { - /* we don't really need to update mval, - * but this happens to be the best way to refresh at the moment */ - knifetool_update_mval_i(kcd, event->mval); + /* We don't really need to update mval, + * but this happens to be the best way to refresh at the moment. */ + knifetool_update_mval_i(C, kcd, event->mval); } - /* keep going until the user confirms */ + /* Keep going until the user confirms. */ return OPERATOR_RUNNING_MODAL; } @@ -3064,34 +4683,59 @@ static int knifetool_invoke(bContext *C, wmOperator *op, const wmEvent *event) { const bool only_select = RNA_boolean_get(op->ptr, "only_selected"); const bool cut_through = !RNA_boolean_get(op->ptr, "use_occlude_geometry"); + const bool xray = !RNA_boolean_get(op->ptr, "xray"); + const int visible_measurements = RNA_enum_get(op->ptr, "visible_measurements"); + const int angle_snapping = RNA_enum_get(op->ptr, "angle_snapping"); const bool wait_for_input = RNA_boolean_get(op->ptr, "wait_for_input"); + const float angle_snapping_increment = RAD2DEGF( + RNA_float_get(op->ptr, "angle_snapping_increment")); ViewContext vc; KnifeTool_OpData *kcd; em_setup_viewcontext(C, &vc); + /* alloc new customdata */ + kcd = op->customdata = MEM_callocN(sizeof(KnifeTool_OpData), __func__); + + knifetool_init(C, + &vc, + kcd, + only_select, + cut_through, + xray, + visible_measurements, + angle_snapping, + angle_snapping_increment, + true); + if (only_select) { - Object *obedit = CTX_data_edit_object(C); - BMEditMesh *em = BKE_editmesh_from_object(obedit); - if (em->bm->totfacesel == 0) { + Object *obedit; + BMEditMesh *em; + bool faces_selected = false; + + for (uint b = 0; b < kcd->objects_len; b++) { + obedit = kcd->objects[b]; + em = BKE_editmesh_from_object(obedit); + if (em->bm->totfacesel != 0) { + faces_selected = true; + } + } + + if (!faces_selected) { BKE_report(op->reports, RPT_ERROR, "Selected faces required"); + knifetool_cancel(C, op); return OPERATOR_CANCELLED; } } - /* alloc new customdata */ - kcd = op->customdata = MEM_callocN(sizeof(KnifeTool_OpData), __func__); - - knifetool_init(&vc, kcd, only_select, cut_through, true); - op->flag |= OP_IS_MODAL_CURSOR_REGION; - /* add a modal handler for this operator - handles loop selection */ + /* Add a modal handler for this operator - handles loop selection. */ WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_KNIFE); WM_event_add_modal_handler(C, op); - knifetool_update_mval_i(kcd, event->mval); + knifetool_update_mval_i(C, kcd, event->mval); if (wait_for_input == false) { /* Avoid copy-paste logic. */ @@ -3112,28 +4756,72 @@ static int knifetool_invoke(bContext *C, wmOperator *op, const wmEvent *event) void MESH_OT_knife_tool(wmOperatorType *ot) { - /* description */ + /* Description. */ ot->name = "Knife Topology Tool"; ot->idname = "MESH_OT_knife_tool"; ot->description = "Cut new topology"; - /* callbacks */ + /* Callbacks. */ ot->invoke = knifetool_invoke; ot->modal = knifetool_modal; ot->cancel = knifetool_cancel; ot->poll = ED_operator_editmesh_view3d; - /* flags */ + /* Flags. */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; - /* properties */ + /* Properties. */ PropertyRNA *prop; + static const EnumPropertyItem visible_measurements_items[] = { + {KNF_MEASUREMENT_NONE, "NONE", 0, "None", "Show no measurements"}, + {KNF_MEASUREMENT_BOTH, "BOTH", 0, "Both", "Show both distances and angles"}, + {KNF_MEASUREMENT_DISTANCE, "DISTANCE", 0, "Distance", "Show just distance measurements"}, + {KNF_MEASUREMENT_ANGLE, "ANGLE", 0, "Angle", "Show just angle measurements"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem angle_snapping_items[] = { + {KNF_CONSTRAIN_ANGLE_MODE_NONE, "NONE", 0, "None", "No angle snapping"}, + {KNF_CONSTRAIN_ANGLE_MODE_SCREEN, "SCREEN", 0, "Screen", "Screen space angle snapping"}, + {KNF_CONSTRAIN_ANGLE_MODE_RELATIVE, + "RELATIVE", + 0, + "Relative", + "Angle snapping relative to the previous cut edge"}, + {0, NULL, 0, NULL, NULL}, + }; + RNA_def_boolean(ot->srna, "use_occlude_geometry", true, "Occlude Geometry", "Only cut the front most geometry"); RNA_def_boolean(ot->srna, "only_selected", false, "Only Selected", "Only cut selected geometry"); + RNA_def_boolean(ot->srna, "xray", true, "X-Ray", "Show cuts through geometry"); + + RNA_def_enum(ot->srna, + "visible_measurements", + visible_measurements_items, + KNF_MEASUREMENT_NONE, + "Measurements", + "Visible distance and angle measurements"); + RNA_def_enum(ot->srna, + "angle_snapping", + angle_snapping_items, + KNF_CONSTRAIN_ANGLE_MODE_NONE, + "Angle Snapping", + "Angle snapping mode"); + + prop = RNA_def_float(ot->srna, + "angle_snapping_increment", + DEG2RADF(KNIFE_DEFAULT_ANGLE_SNAPPING_INCREMENT), + DEG2RADF(KNIFE_MIN_ANGLE_SNAPPING_INCREMENT), + DEG2RADF(KNIFE_MAX_ANGLE_SNAPPING_INCREMENT), + "Angle Snap Increment", + "The angle snap increment used when in constrained angle mode", + DEG2RADF(KNIFE_MIN_ANGLE_SNAPPING_INCREMENT), + DEG2RADF(KNIFE_MAX_ANGLE_SNAPPING_INCREMENT)); + RNA_def_property_subtype(prop, PROP_ANGLE); prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); @@ -3168,32 +4856,41 @@ static bool edbm_mesh_knife_point_isect(LinkNode *polys, const float cent_ss[2]) /** * \param use_tag: When set, tag all faces inside the polylines. */ -void EDBM_mesh_knife(ViewContext *vc, LinkNode *polys, bool use_tag, bool cut_through) +void EDBM_mesh_knife(bContext *C, ViewContext *vc, LinkNode *polys, bool use_tag, bool cut_through) { KnifeTool_OpData *kcd; - /* init */ + /* Init. */ { const bool only_select = false; - const bool is_interactive = false; /* can enable for testing */ + const bool is_interactive = false; /* Can enable for testing. */ + const bool xray = false; + const int visible_measurements = KNF_MEASUREMENT_NONE; + const int angle_snapping = KNF_CONSTRAIN_ANGLE_MODE_NONE; + const float angle_snapping_increment = KNIFE_DEFAULT_ANGLE_SNAPPING_INCREMENT; kcd = MEM_callocN(sizeof(KnifeTool_OpData), __func__); - knifetool_init(vc, kcd, only_select, cut_through, is_interactive); + knifetool_init(C, + vc, + kcd, + only_select, + cut_through, + xray, + visible_measurements, + angle_snapping, + angle_snapping_increment, + is_interactive); kcd->ignore_edge_snapping = true; kcd->ignore_vert_snapping = true; - - if (use_tag) { - BM_mesh_elem_hflag_enable_all(kcd->em->bm, BM_EDGE, BM_ELEM_TAG, false); - } } - /* execute */ + /* Execute. */ { LinkNode *p = polys; - knife_recalc_projmat(kcd); + knife_recalc_ortho(kcd); while (p) { const float(*mval_fl)[2] = p->link; @@ -3201,7 +4898,7 @@ void EDBM_mesh_knife(ViewContext *vc, LinkNode *polys, bool use_tag, bool cut_th int i; for (i = 0; i < mval_tot; i++) { - knifetool_update_mval(kcd, mval_fl[i]); + knifetool_update_mval(C, kcd, mval_fl[i]); if (i == 0) { knife_start_cut(kcd); kcd->mode = MODE_DRAGGING; @@ -3216,104 +4913,108 @@ void EDBM_mesh_knife(ViewContext *vc, LinkNode *polys, bool use_tag, bool cut_th } } - /* finish */ + /* Finish. */ { - knifetool_finish_ex(kcd); - - /* tag faces inside! */ - if (use_tag) { - BMesh *bm = kcd->em->bm; - float projmat[4][4]; - - BMEdge *e; - BMIter iter; + Object *ob; + BMEditMesh *em; + for (uint b = 0; b < kcd->objects_len; b++) { - bool keep_search; + ob = kcd->objects[b]; + em = BKE_editmesh_from_object(ob); - /* freed on knifetool_finish_ex, but we need again to check if points are visible */ - if (kcd->cut_through == false) { - knifetool_init_bmbvh(kcd); + if (use_tag) { + BM_mesh_elem_hflag_enable_all(em->bm, BM_EDGE, BM_ELEM_TAG, false); } - ED_view3d_ob_project_mat_get(kcd->region->regiondata, kcd->ob, projmat); + knifetool_finish_single_ex(kcd, ob, b); - /* use face-loop tag to store if we have intersected */ + /* Tag faces inside! */ + if (use_tag) { + BMesh *bm = em->bm; + BMEdge *e; + BMIter iter; + bool keep_search; + + /* Use face-loop tag to store if we have intersected. */ #define F_ISECT_IS_UNKNOWN(f) BM_elem_flag_test(BM_FACE_FIRST_LOOP(f), BM_ELEM_TAG) #define F_ISECT_SET_UNKNOWN(f) BM_elem_flag_enable(BM_FACE_FIRST_LOOP(f), BM_ELEM_TAG) #define F_ISECT_SET_OUTSIDE(f) BM_elem_flag_disable(BM_FACE_FIRST_LOOP(f), BM_ELEM_TAG) - { - BMFace *f; - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - F_ISECT_SET_UNKNOWN(f); - BM_elem_flag_disable(f, BM_ELEM_TAG); - } - } - - /* tag all faces linked to cut edges */ - BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { - /* check are we tagged?, then we are an original face */ - if (BM_elem_flag_test(e, BM_ELEM_TAG) == false) { + { BMFace *f; - BMIter fiter; - BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) { - float cent[3], cent_ss[2]; - BM_face_calc_point_in_face(f, cent); - knife_project_v2(kcd, cent, cent_ss); - if (edbm_mesh_knife_point_isect(polys, cent_ss)) { - BM_elem_flag_enable(f, BM_ELEM_TAG); - } + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + F_ISECT_SET_UNKNOWN(f); + BM_elem_flag_disable(f, BM_ELEM_TAG); } } - } - - /* expand tags for faces which are not cut, but are inside the polys */ - do { - BMFace *f; - keep_search = false; - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(f, BM_ELEM_TAG) == false && (F_ISECT_IS_UNKNOWN(f))) { - /* am I connected to a tagged face via an un-tagged edge - * (ie, not across a cut) */ - BMLoop *l_first = BM_FACE_FIRST_LOOP(f); - BMLoop *l_iter = l_first; - bool found = false; - - do { - if (BM_elem_flag_test(l_iter->e, BM_ELEM_TAG) != false) { - /* now check if the adjacent faces is tagged */ - BMLoop *l_radial_iter = l_iter->radial_next; - if (l_radial_iter != l_iter) { - do { - if (BM_elem_flag_test(l_radial_iter->f, BM_ELEM_TAG)) { - found = true; - } - } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter && - (found == false)); - } - } - } while ((l_iter = l_iter->next) != l_first && (found == false)); - if (found) { + /* Tag all faces linked to cut edges. */ + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + /* Check are we tagged?, then we are an original face. */ + if (BM_elem_flag_test(e, BM_ELEM_TAG) == false) { + BMFace *f; + BMIter fiter; + BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) { float cent[3], cent_ss[2]; BM_face_calc_point_in_face(f, cent); + mul_m4_v3(ob->obmat, cent); knife_project_v2(kcd, cent, cent_ss); - if ((kcd->cut_through || point_is_visible(kcd, cent, cent_ss, (BMElem *)f)) && - edbm_mesh_knife_point_isect(polys, cent_ss)) { + if (edbm_mesh_knife_point_isect(polys, cent_ss)) { BM_elem_flag_enable(f, BM_ELEM_TAG); - keep_search = true; - } - else { - /* don't lose time on this face again, set it as outside */ - F_ISECT_SET_OUTSIDE(f); } } } } - } while (keep_search); + + /* Expand tags for faces which are not cut, but are inside the polys. */ + do { + BMFace *f; + keep_search = false; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_TAG) == false && (F_ISECT_IS_UNKNOWN(f))) { + /* Am I connected to a tagged face via an un-tagged edge + * (ie, not across a cut)? */ + BMLoop *l_first = BM_FACE_FIRST_LOOP(f); + BMLoop *l_iter = l_first; + bool found = false; + + do { + if (BM_elem_flag_test(l_iter->e, BM_ELEM_TAG) != false) { + /* Now check if the adjacent faces is tagged. */ + BMLoop *l_radial_iter = l_iter->radial_next; + if (l_radial_iter != l_iter) { + do { + if (BM_elem_flag_test(l_radial_iter->f, BM_ELEM_TAG)) { + found = true; + } + } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter && + (found == false)); + } + } + } while ((l_iter = l_iter->next) != l_first && (found == false)); + + if (found) { + float cent[3], cent_ss[2]; + BM_face_calc_point_in_face(f, cent); + mul_m4_v3(ob->obmat, cent); + knife_project_v2(kcd, cent, cent_ss); + if ((kcd->cut_through || point_is_visible(kcd, cent, cent_ss, (BMElem *)f)) && + edbm_mesh_knife_point_isect(polys, cent_ss)) { + BM_elem_flag_enable(f, BM_ELEM_TAG); + keep_search = true; + } + else { + /* Don't lose time on this face again, set it as outside. */ + F_ISECT_SET_OUTSIDE(f); + } + } + } + } + } while (keep_search); #undef F_ISECT_IS_UNKNOWN #undef F_ISECT_SET_UNKNOWN #undef F_ISECT_SET_OUTSIDE + } } knifetool_exit_ex(kcd); diff --git a/source/blender/editors/mesh/editmesh_knife_project.c b/source/blender/editors/mesh/editmesh_knife_project.c index 669a09b3fd3..3129fb047ab 100644 --- a/source/blender/editors/mesh/editmesh_knife_project.c +++ b/source/blender/editors/mesh/editmesh_knife_project.c @@ -158,7 +158,7 @@ static int knifeproject_exec(bContext *C, wmOperator *op) ED_view3d_viewcontext_init_object(&vc, obedit); BMEditMesh *em = BKE_editmesh_from_object(obedit); - EDBM_mesh_knife(&vc, polys, true, cut_through); + EDBM_mesh_knife(C, &vc, polys, true, cut_through); /* select only tagged faces */ BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false); diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index 03c99e40d1e..abff3c70e67 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -150,7 +150,8 @@ void MESH_OT_face_split_by_edges(struct wmOperatorType *ot); /* *** editmesh_knife.c *** */ void MESH_OT_knife_tool(struct wmOperatorType *ot); void MESH_OT_knife_project(struct wmOperatorType *ot); -void EDBM_mesh_knife(struct ViewContext *vc, +void EDBM_mesh_knife(struct bContext *C, + struct ViewContext *vc, struct LinkNode *polys, bool use_tag, bool cut_through); |