Cleanup: style, whitespace, doxy filepaths
[blender-staging.git] / source / blender / editors / mesh / editmesh_knife.c
index f5a7e82a8a8cad10215de852a94aa1923a15808a..a84b8d9dcc82f21644b5c82fb2793698aa754a2c 100644 (file)
@@ -46,7 +46,7 @@
 #include "BLI_smallhash.h"
 #include "BLI_memarena.h"
 
-#include "BLF_translation.h"
+#include "BLT_translation.h"
 
 #include "BKE_DerivedMesh.h"
 #include "BKE_context.h"
@@ -66,6 +66,8 @@
 #include "WM_types.h"
 
 #include "DNA_object_types.h"
+
+#include "UI_interface.h"
 #include "UI_resources.h"
 
 #include "RNA_access.h"
 
 #include "mesh_intern.h"  /* own include */
 
+/* detect isolated holes and fill them */
+#define USE_NET_ISLAND_CONNECT
+
 #define KMAXDIST    10  /* 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).
+ */
 #define KNIFE_FLT_EPS          0.00001f
 #define KNIFE_FLT_EPS_SQUARED  (KNIFE_FLT_EPS * KNIFE_FLT_EPS)
 #define KNIFE_FLT_EPSBIG       0.0005f
-#define KNIFE_FLT_EPS_PX       0.2f
+
+#define KNIFE_FLT_EPS_PX_VERT  0.5f
+#define KNIFE_FLT_EPS_PX_EDGE  0.05f
+#define KNIFE_FLT_EPS_PX_FACE  0.05f
 
 typedef struct KnifeColors {
        unsigned char line[3];
@@ -97,7 +108,7 @@ typedef struct KnifeVert {
 
        float co[3], cageco[3], sco[2]; /* sco is screen coordinates for cageco */
        bool is_face, in_space;
-       bool draw;
+       bool is_cut;  /* along a cut created by user input (will draw too) */
 } KnifeVert;
 
 typedef struct Ref {
@@ -111,7 +122,7 @@ typedef struct KnifeEdge {
        ListBase faces;
 
        BMEdge *e /* , *e_old */; /* non-NULL if this is an original edge */
-       bool draw;
+       bool is_cut;  /* along a cut created by user input (will draw too) */
 } KnifeEdge;
 
 typedef struct KnifeLineHit {
@@ -152,11 +163,21 @@ typedef struct KnifeTool_OpData {
        float mval[2];      /* mouse value with snapping applied */
        //bContext *C;
 
+       Scene *scene;
        Object *ob;
        BMEditMesh *em;
 
        MemArena *arena;
 
+       /* reused for edge-net filling */
+       struct {
+               /* cleared each use */
+               GSet *edge_visit;
+#ifdef USE_NET_ISLAND_CONNECT
+               MemArena *arena;
+#endif
+       } edgenet;
+
        GHash *origvertmap;
        GHash *origedgemap;
        GHash *kedgefacemap;
@@ -174,8 +195,10 @@ typedef struct KnifeTool_OpData {
        KnifeLineHit *linehits;
        int totlinehit;
 
-       /* Data for mouse-position-derived data (cur) and previous click (prev) */
-       KnifePosData curr, prev;
+       /* 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 */
 
        int totkedge, totkvert;
 
@@ -198,6 +221,8 @@ typedef struct KnifeTool_OpData {
 
        bool is_ortho;
        float ortho_extent;
+       float ortho_extent_center[3];
+
        float clipsta, clipend;
 
        enum {
@@ -206,6 +231,7 @@ typedef struct KnifeTool_OpData {
                MODE_CONNECT,
                MODE_PANNING
        } mode;
+       bool is_drag_hold;
 
        int prevmode;
        bool snap_midpoints;
@@ -214,18 +240,28 @@ typedef struct KnifeTool_OpData {
 
        /* use to check if we're currently dragging an angle snapped line */
        bool is_angle_snapping;
-
-       enum {
-               ANGLE_FREE,
-               ANGLE_0,
-               ANGLE_45,
-               ANGLE_90,
-               ANGLE_135
-       } angle_snapping;
+       bool angle_snapping;
+       float angle;
 
        const float (*cagecos)[3];
 } KnifeTool_OpData;
 
+enum {
+       KNF_MODAL_CANCEL = 1,
+       KNF_MODAL_CONFIRM,
+       KNF_MODAL_MIDPOINT_ON,
+       KNF_MODAL_MIDPOINT_OFF,
+       KNF_MODAL_NEW_CUT,
+       KNF_MODEL_IGNORE_SNAP_ON,
+       KNF_MODEL_IGNORE_SNAP_OFF,
+       KNF_MODAL_ADD_CUT,
+       KNF_MODAL_ANGLE_SNAP_TOGGLE,
+       KNF_MODAL_CUT_THROUGH_TOGGLE,
+       KNF_MODAL_PANNING,
+       KNF_MODAL_ADD_CUT_CLOSED,
+};
+
+
 static ListBase *knife_get_face_kedges(KnifeTool_OpData *kcd, BMFace *f);
 
 static void knife_input_ray_segment(KnifeTool_OpData *kcd, const float mval[2], const float ofs,
@@ -233,20 +269,35 @@ static void knife_input_ray_segment(KnifeTool_OpData *kcd, const float mval[2],
 
 static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f);
 
-static void knife_update_header(bContext *C, KnifeTool_OpData *kcd)
+static void knifetool_free_bmbvh(KnifeTool_OpData *kcd);
+
+static void knife_update_header(bContext *C, wmOperator *op, KnifeTool_OpData *kcd)
 {
-#define HEADER_LENGTH 256
-       char header[HEADER_LENGTH];
+       char header[UI_MAX_DRAW_STR];
+       char buf[UI_MAX_DRAW_STR];
+
+       char *p = buf;
+       int available_len = sizeof(buf);
+
+#define WM_MODALKEY(_id) \
+       WM_modalkeymap_operator_items_to_string_buf(op->type, (_id), true, UI_MAX_SHORTCUT_STR, &available_len, &p)
+
+       BLI_snprintf(header, sizeof(header), IFACE_("%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_MODEL_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));
+
+#undef WM_MODALKEY
 
-       BLI_snprintf(header, HEADER_LENGTH, IFACE_("LMB: define cut lines, Return/Spacebar: confirm, Esc or RMB: cancel, "
-                                                  "E: new cut, Ctrl: midpoint snap (%s), Shift: ignore snap (%s), "
-                                                  "C: angle constrain (%s), Z: cut through (%s)"),
-                    WM_bool_as_string(kcd->snap_midpoints),
-                    WM_bool_as_string(kcd->ignore_edge_snapping),
-                    WM_bool_as_string(kcd->angle_snapping),
-                    WM_bool_as_string(kcd->cut_through));
        ED_area_headerprint(CTX_wm_area(C), header);
-#undef HEADER_LENGTH
 }
 
 static void knife_project_v2(const KnifeTool_OpData *kcd, const float co[3], float sco[2])
@@ -526,7 +577,7 @@ static KnifeVert *knife_split_edge(
 
        newkfe->v1 = kfe->v1;
        newkfe->v2 = new_knife_vert(kcd, co, cageco);
-       newkfe->v2->draw = 1;
+       newkfe->v2->is_cut = true;
        if (kfe->e) {
                knife_add_edge_faces_to_vert(kcd, newkfe->v2, kfe->e);
        }
@@ -551,7 +602,7 @@ static KnifeVert *knife_split_edge(
 
        knife_add_to_vert_edges(kcd, newkfe);
 
-       newkfe->draw = kfe->draw;
+       newkfe->is_cut = kfe->is_cut;
        newkfe->e = kfe->e;
 
        *r_kfe = newkfe;
@@ -559,6 +610,16 @@ static KnifeVert *knife_split_edge(
        return newkfe->v2;
 }
 
+static void linehit_to_knifepos(KnifePosData *kpos, KnifeLineHit *lh)
+{
+       kpos->bmface = lh->f;
+       kpos->vert = lh->v;
+       kpos->edge = lh->kfe;
+       copy_v3_v3(kpos->cage, lh->cagehit);
+       copy_v3_v3(kpos->co, lh->hit);
+       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
@@ -590,6 +651,7 @@ static void prepare_linehits_for_cut(KnifeTool_OpData *kcd)
 {
        KnifeLineHit *linehits, *lhi, *lhj;
        int i, j, n;
+       bool is_double = false;
 
        n = kcd->totlinehit;
        linehits = kcd->linehits;
@@ -613,7 +675,11 @@ static void prepare_linehits_for_cut(KnifeTool_OpData *kcd)
                                {
                                        break;
                                }
-                               lhj->l = -1.0f;
+
+                               if (lhi->kfe == lhj->kfe) {
+                                       lhj->l = -1.0f;
+                                       is_double = true;
+                               }
                        }
                        for (j = i + 1; j < n; j++) {
                                lhj = &linehits[j];
@@ -622,37 +688,42 @@ static void prepare_linehits_for_cut(KnifeTool_OpData *kcd)
                                {
                                        break;
                                }
-                               if (lhj->kfe || lhi->v == lhj->v) {
+                               if ((lhj->kfe && (lhi->kfe == lhj->kfe)) ||
+                                   (lhi->v == lhj->v))
+                               {
                                        lhj->l = -1.0f;
+                                       is_double = true;
                                }
                        }
                }
        }
 
-       /* delete-in-place loop: copying from pos j to pos i+1 */
-       i = 0;
-       j = 1;
-       while (j < n) {
-               lhi = &linehits[i];
-               lhj = &linehits[j];
-               if (lhj->l == -1.0f) {
-                       j++; /* skip copying this one */
-               }
-               else {
-                       /* copy unless a no-op */
-                       if (lhi->l == -1.0f) {
-                               /* could happen if linehits[0] is being deleted */
-                               memcpy(&linehits[i], &linehits[j], sizeof(KnifeLineHit));
+       if (is_double) {
+               /* delete-in-place loop: copying from pos j to pos i+1 */
+               i = 0;
+               j = 1;
+               while (j < n) {
+                       lhi = &linehits[i];
+                       lhj = &linehits[j];
+                       if (lhj->l == -1.0f) {
+                               j++; /* skip copying this one */
                        }
                        else {
-                               if (i + 1 != j)
-                                       memcpy(&linehits[i + 1], &linehits[j], sizeof(KnifeLineHit));
-                               i++;
+                               /* copy unless a no-op */
+                               if (lhi->l == -1.0f) {
+                                       /* could happen if linehits[0] is being deleted */
+                                       memcpy(&linehits[i], &linehits[j], sizeof(KnifeLineHit));
+                               }
+                               else {
+                                       if (i + 1 != j)
+                                               memcpy(&linehits[i + 1], &linehits[j], sizeof(KnifeLineHit));
+                                       i++;
+                               }
+                               j++;
                        }
-                       j++;
                }
+               kcd->totlinehit = i + 1;
        }
-       kcd->totlinehit = i + 1;
 }
 
 /* Add hit to list of hits in facehits[f], where facehits is a map, if not already there */
@@ -667,9 +738,38 @@ static void add_hit_to_facehits(KnifeTool_OpData *kcd, GHash *facehits, BMFace *
        knife_append_list_no_dup(kcd, lst, hit);
 }
 
+/**
+ * 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, const float co[3])
+{
+
+       if (lh->v && lh->v->v) {
+               BMLoop *l;  /* side-of-loop */
+               if ((l = BM_face_vert_share_loop(f, lh->v->v)) &&
+                   (BM_loop_point_side_of_loop_test(l, co) < 0.0f))
+               {
+                       return true;
+               }
+       }
+       else if ((lh->kfe && lh->kfe->e)) {
+               BMLoop *l;  /* side-of-edge */
+               if ((l = BM_face_edge_share_loop(f, lh->kfe->e)) &&
+                   (BM_loop_point_side_of_edge_test(l, co) < 0.0f))
+               {
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+
 static void knife_add_single_cut(KnifeTool_OpData *kcd, KnifeLineHit *lh1, KnifeLineHit *lh2, BMFace *f)
 {
        KnifeEdge *kfe, *kfe2;
+       BMEdge *e_base;
 
        if ((lh1->v && lh1->v == lh2->v) ||
            (lh1->kfe && lh1->kfe == lh2->kfe))
@@ -677,6 +777,25 @@ static void knife_add_single_cut(KnifeTool_OpData *kcd, KnifeLineHit *lh1, Knife
                return;
        }
 
+       /* if the cut is on an edge, just tag that its a cut and return */
+       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;
+       }
+       else {
+               if (knife_add_single_cut__is_linehit_outside_face(f, lh1, lh2->hit) ||
+                   knife_add_single_cut__is_linehit_outside_face(f, lh2, lh1->hit))
+               {
+                       return;
+               }
+       }
+
+
        /* 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)) {
@@ -685,7 +804,7 @@ static void knife_add_single_cut(KnifeTool_OpData *kcd, KnifeLineHit *lh1, Knife
        }
 
        kfe = new_knife_edge(kcd);
-       kfe->draw = true;
+       kfe->is_cut = true;
        kfe->basef = f;
 
        if (lh1->v) {
@@ -698,7 +817,7 @@ static void knife_add_single_cut(KnifeTool_OpData *kcd, KnifeLineHit *lh1, Knife
        else {
                BLI_assert(lh1->f);
                kfe->v1 = new_knife_vert(kcd, lh1->hit, lh1->cagehit);
-               kfe->v1->draw = true;
+               kfe->v1->is_cut = true;
                kfe->v1->is_face = true;
                knife_append_list(kcd, &kfe->v1->faces, lh1->f);
                lh1->v = kfe->v1;  /* record the KnifeVert for this hit */
@@ -714,7 +833,7 @@ static void knife_add_single_cut(KnifeTool_OpData *kcd, KnifeLineHit *lh1, Knife
        else {
                BLI_assert(lh2->f);
                kfe->v2 = new_knife_vert(kcd, lh2->hit, lh2->cagehit);
-               kfe->v2->draw = true;
+               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 */
@@ -735,23 +854,13 @@ static void knife_add_single_cut(KnifeTool_OpData *kcd, KnifeLineHit *lh1, Knife
 static void knife_cut_face(KnifeTool_OpData *kcd, BMFace *f, ListBase *hits)
 {
        Ref *r;
-       KnifeLineHit *lh, *prevlh;
-       int n;
 
-       (void) kcd;
-
-       n = BLI_countlist(hits);
-       if (n < 2)
+       if (BLI_listbase_count_ex(hits, 2) != 2)
                return;
 
-       prevlh = NULL;
-       for (r = hits->first; r; r = r->next) {
-               lh = (KnifeLineHit *)r->ref;
-               if (prevlh)
-                       knife_add_single_cut(kcd, prevlh, lh, f);
-               prevlh = lh;
+       for (r = hits->first; r->next; r = r->next) {
+               knife_add_single_cut(kcd, r->ref, r->next->ref, f);
        }
-
 }
 
 /* User has just left-clicked after the first time.
@@ -762,7 +871,6 @@ static void knife_cut_face(KnifeTool_OpData *kcd, BMFace *f, ListBase *hits)
 static void knife_add_cut(KnifeTool_OpData *kcd)
 {
        int i;
-       KnifeLineHit *lh;
        GHash *facehits;
        BMFace *f;
        Ref *r;
@@ -771,14 +879,16 @@ static void knife_add_cut(KnifeTool_OpData *kcd)
 
        prepare_linehits_for_cut(kcd);
        if (kcd->totlinehit == 0) {
-               kcd->prev = kcd->curr;
+               if (kcd->is_drag_hold == false) {
+                       kcd->prev = kcd->curr;
+               }
                return;
        }
 
        /* make facehits: map face -> list of linehits touching it */
        facehits = BLI_ghash_ptr_new("knife facehits");
        for (i = 0; i < kcd->totlinehit; i++) {
-               lh = &kcd->linehits[i];
+               KnifeLineHit *lh = &kcd->linehits[i];
                if (lh->f) {
                        add_hit_to_facehits(kcd, facehits, lh->f, lh);
                }
@@ -806,10 +916,18 @@ static void knife_add_cut(KnifeTool_OpData *kcd)
 
        /* 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 */
+               KnifeLineHit *lh = &kcd->linehits[kcd->totlinehit - 1];
+               kcd->prev.vert = lh->v;
                kcd->prev.bmface = NULL;
-               kcd->prev.vert = kcd->linehits[kcd->totlinehit - 1].v;
+       }
+
+       if (kcd->is_drag_hold) {
+               KnifeLineHit *lh = &kcd->linehits[kcd->totlinehit - 1];
+               linehit_to_knifepos(&kcd->prev, lh);
        }
 
        BLI_ghash_free(facehits, NULL, NULL);
@@ -829,98 +947,66 @@ static void knife_finish_cut(KnifeTool_OpData *kcd)
 
 static void knifetool_draw_angle_snapping(const KnifeTool_OpData *kcd)
 {
-       bglMats mats;
-       double u[3], u1[2], u2[2], v1[3], v2[3], dx, dy;
-       double wminx, wminy, wmaxx, wmaxy;
-
-       /* make u the window coords of prevcage */
-       view3d_get_transformation(kcd->ar, kcd->vc.rv3d, kcd->ob, &mats);
-       gluProject(kcd->prev.cage[0], kcd->prev.cage[1], kcd->prev.cage[2],
-                  mats.modelview, mats.projection, mats.viewport,
-                  &u[0], &u[1], &u[2]);
-
-       /* make u1, u2 the points on window going through u at snap angle */
-       wminx = kcd->ar->winrct.xmin;
-       wmaxx = kcd->ar->winrct.xmin + kcd->ar->winx;
-       wminy = kcd->ar->winrct.ymin;
-       wmaxy = kcd->ar->winrct.ymin + kcd->ar->winy;
-
-       switch (kcd->angle_snapping) {
-               case ANGLE_0:
-                       u1[0] = wminx;
-                       u2[0] = wmaxx;
-                       u1[1] = u2[1] = u[1];
-                       break;
-               case ANGLE_90:
-                       u1[0] = u2[0] = u[0];
-                       u1[1] = wminy;
-                       u2[1] = wmaxy;
-                       break;
-               case ANGLE_45:
-                       /* clip against left or bottom */
-                       dx = u[0] - wminx;
-                       dy = u[1] - wminy;
-                       if (dy > dx) {
-                               u1[0] = wminx;
-                               u1[1] = u[1] - dx;
-                       }
-                       else {
-                               u1[0] = u[0] - dy;
-                               u1[1] = wminy;
-                       }
-                       /* clip against right or top */
-                       dx = wmaxx - u[0];
-                       dy = wmaxy - u[1];
-                       if (dy > dx) {
-                               u2[0] = wmaxx;
-                               u2[1] = u[1] + dx;
-                       }
-                       else {
-                               u2[0] = u[0] + dy;
-                               u2[1] = wmaxy;
-                       }
-                       break;
-               case ANGLE_135:
-                       /* clip against right or bottom */
-                       dx = wmaxx - u[0];
-                       dy = u[1] - wminy;
-                       if (dy > dx) {
-                               u1[0] = wmaxx;
-                               u1[1] = u[1] - dx;
-                       }
-                       else {
-                               u1[0] = u[0] + dy;
-                               u1[1] = wminy;
-                       }
-                       /* clip against left or top */
-                       dx = u[0] - wminx;
-                       dy = wmaxy - u[1];
-                       if (dy > dx) {
-                               u2[0] = wminx;
-                               u2[1] = u[1] + dx;
-                       }
-                       else {
-                               u2[0] = u[0] - dy;
-                               u2[1] = wmaxy;
+       float v1[3], v2[3];
+       float planes[4][4];
+
+       planes_from_projmat(
+               (float (*)[4])kcd->projmat,
+               planes[2], planes[0], planes[3], planes[1], NULL, NULL);
+
+       /* 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
+               sub_v3_v3v3(ray_dir, kcd->curr.cage, kcd->prev.cage);
+#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->ar, 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
+
+               for (i = 0; i < 4; i++) {
+                       float ray_hit[3];
+                       float lambda_test;
+                       if (isect_ray_plane_v3(kcd->prev.cage, ray_dir, planes[i], &lambda_test, false)) {
+                               madd_v3_v3v3fl(ray_hit, kcd->prev.cage, ray_dir, lambda_test);
+                               if (lambda_test < 0.0f) {
+                                       if (lambda_test > lambda_best[0]) {
+                                               copy_v3_v3(ray_hit_best[0], ray_hit);
+                                               lambda_best[0] = lambda_test;
+                                       }
+                               }
+                               else {
+                                       if (lambda_test < lambda_best[1]) {
+                                               copy_v3_v3(ray_hit_best[1], ray_hit);
+                                               lambda_best[1] = lambda_test;
+                                       }
+                               }
                        }
-                       break;
-               default:
-                       return;
-       }
+               }
 
-       /* unproject u1 and u2 back into object space */
-       gluUnProject(u1[0], u1[1], 0.0,
-                    mats.modelview, mats.projection, mats.viewport,
-                    &v1[0], &v1[1], &v1[2]);
-       gluUnProject(u2[0], u2[1], 0.0,
-                    mats.modelview, mats.projection, mats.viewport,
-                    &v2[0], &v2[1], &v2[2]);
+               copy_v3_v3(v1, ray_hit_best[0]);
+               copy_v3_v3(v2, ray_hit_best[1]);
+       }
 
        UI_ThemeColor(TH_TRANSFORM);
        glLineWidth(2.0);
        glBegin(GL_LINES);
-       glVertex3dv(v1);
-       glVertex3dv(v2);
+       glVertex3fv(v1);
+       glVertex3fv(v2);
        glEnd();
 }
 
@@ -953,7 +1039,7 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
        glMultMatrixf(kcd->ob->obmat);
 
        if (kcd->mode == MODE_DRAGGING) {
-               if (kcd->angle_snapping != ANGLE_FREE)
+               if (kcd->is_angle_snapping)
                        knifetool_draw_angle_snapping(kcd);
 
                glColor3ubv(kcd->colors.line);
@@ -964,8 +1050,6 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
                glVertex3fv(kcd->prev.cage);
                glVertex3fv(kcd->curr.cage);
                glEnd();
-
-               glLineWidth(1.0);
        }
 
        if (kcd->prev.vert) {
@@ -994,8 +1078,6 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
                glVertex3fv(kcd->curr.edge->v1->cageco);
                glVertex3fv(kcd->curr.edge->v2->cageco);
                glEnd();
-
-               glLineWidth(1.0);
        }
        else if (kcd->curr.vert) {
                glColor3ubv(kcd->colors.point);
@@ -1055,7 +1137,7 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
 
                BLI_mempool_iternew(kcd->kedges, &iter);
                for (kfe = BLI_mempool_iterstep(&iter); kfe; kfe = BLI_mempool_iterstep(&iter)) {
-                       if (!kfe->draw)
+                       if (!kfe->is_cut)
                                continue;
 
                        glColor3ubv(kcd->colors.line);
@@ -1065,7 +1147,6 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
                }
 
                glEnd();
-               glLineWidth(1.0);
        }
 
        if (kcd->totkvert > 0) {
@@ -1077,7 +1158,7 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
                glBegin(GL_POINTS);
                BLI_mempool_iternew(kcd->kverts, &iter);
                for (kfv = BLI_mempool_iterstep(&iter); kfv; kfv = BLI_mempool_iterstep(&iter)) {
-                       if (!kfv->draw)
+                       if (!kfv->is_cut)
                                continue;
 
                        glColor3ubv(kcd->colors.point);
@@ -1169,25 +1250,154 @@ static bool knife_ray_intersect_face(
        return false;
 }
 
-/* Calculate maximum excursion from (0,0,0) of mesh */
+/**
+ * Calculate the center and maximum excursion of mesh.
+ */
 static void calc_ortho_extent(KnifeTool_OpData *kcd)
 {
        BMIter iter;
        BMVert *v;
        BMesh *bm = kcd->em->bm;
-       float max_xyz = 0.0f;
-       int i;
+       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);
+               }
+       }
+
+       kcd->ortho_extent = len_v3v3(min, max) / 2;
+       mid_v3_v3v3(kcd->ortho_extent_center, min, max);
+}
+
+static BMElem *bm_elem_from_knife_vert(KnifeVert *kfv, KnifeEdge **r_kfe)
+{
+       BMElem *ele_test;
+       KnifeEdge *kfe = NULL;
+
+       /* vert? */
+       ele_test = (BMElem *)kfv->v;
 
-       BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
-               for (i = 0; i < 3; i++)
-                       max_xyz = max_ff(max_xyz, fabsf(v->co[i]));
+       if (r_kfe || ele_test == NULL) {
+               if (kfv->v == NULL) {
+                       Ref *ref;
+                       for (ref = kfv->edges.first; ref; ref = ref->next) {
+                               kfe = ref->ref;
+                               if (kfe->e) {
+                                       if (r_kfe) {
+                                               *r_kfe = kfe;
+                                       }
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       /* edge? */
+       if (ele_test == NULL) {
+               if (kfe) {
+                       ele_test = (BMElem *)kfe->e;
+               }
+       }
+
+       /* face? */
+       if (ele_test == NULL) {
+               if (BLI_listbase_is_single(&kfe->faces)) {
+                       ele_test = ((Ref *)kfe->faces.first)->ref;
+               }
+       }
+
+       return ele_test;
+}
+
+static BMElem *bm_elem_from_knife_edge(KnifeEdge *kfe)
+{
+       BMElem *ele_test;
+
+       ele_test = (BMElem *)kfe->e;
+
+       if (ele_test == NULL) {
+               ele_test = (BMElem *)kfe->basef;
+       }
+
+       return ele_test;
+}
+
+/* Do edges e1 and e2 go between exactly the same coordinates? */
+static bool coinciding_edges(BMEdge *e1, BMEdge *e2)
+{
+       const float *co11, *co12, *co21, *co22;
+
+       co11 = e1->v1->co;
+       co12 = e1->v2->co;
+       co21 = e2->v1->co;
+       co22 = e2->v2->co;
+       if ((equals_v3v3(co11, co21) && equals_v3v3(co12, co22)) ||
+           (equals_v3v3(co11, co22) && equals_v3v3(co12, co21)))
+       {
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+/* Callback used in point_is_visible to exclude hits on the faces that are the same
+ * as or contain the hitting element (which is in user_data).
+ * Also (see T44492) want to exclude hits on faces that butt up to the hitting element
+ * (e.g., when you double an edge by an edge split).
+ */
+static bool bm_ray_cast_cb_elem_not_in_face_check(BMFace *f, void *user_data)
+{
+       bool ans;
+       BMEdge *e, *e2;
+       BMIter iter;
+
+       switch (((BMElem *)user_data)->head.htype) {
+               case BM_FACE:
+                       ans = (BMFace *)user_data != f;
+                       break;
+               case BM_EDGE:
+                       e = (BMEdge *)user_data;
+                       ans = !BM_edge_in_face(e, f);
+                       if (ans) {
+                               /* Is it a boundary edge, coincident with a split edge? */
+                               if (BM_edge_is_boundary(e)) {
+                                       BM_ITER_ELEM(e2, &iter, f, BM_EDGES_OF_FACE) {
+                                               if (coinciding_edges(e, e2)) {
+                                                       ans = false;
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+                       break;
+               case BM_VERT:
+                       ans = !BM_vert_in_face((BMVert *)user_data, f);
+                       break;
+               default:
+                       ans = true;
+                       break;
        }
-       kcd->ortho_extent = max_xyz;
+       return ans;
 }
 
-/* Check if p is visible (not clipped, not occluded by another face).
- * s in screen projection of p. */
-static bool point_is_visible(KnifeTool_OpData *kcd, const float p[3], const float s[2], bglMats *mats)
+
+/**
+ * Check if \a p is visible (not clipped, not occluded by another face).
+ * s in screen projection of p.
+ *
+ * \param ele_test  Optional vert/edge/face to use when \a p is on the surface of the geometry,
+ * intersecting faces matching this face (or connected when an vert/edge) will be ignored.
+ */
+static bool point_is_visible(
+        KnifeTool_OpData *kcd, const float p[3], const float s[2], bglMats *mats,
+        BMElem *ele_test)
 {
        BMFace *f_hit;
 
@@ -1211,7 +1421,7 @@ static bool point_is_visible(KnifeTool_OpData *kcd, const float p[3], const floa
                /* make p_ofs a little towards view, so ray doesn't hit p's face. */
                sub_v3_v3(view, p);
                dist = normalize_v3(view);
-               madd_v3_v3v3fl(p_ofs, p, view, KNIFE_FLT_EPSBIG * 3.0f);
+               copy_v3_v3(p_ofs, p);
 
                /* avoid projecting behind the viewpoint */
                if (kcd->is_ortho && (kcd->vc.rv3d->persp != RV3D_CAMOB)) {
@@ -1224,15 +1434,28 @@ static bool point_is_visible(KnifeTool_OpData *kcd, const float p[3], const floa
                        copy_v3_v3(view_clip[0], p_ofs);
                        madd_v3_v3v3fl(view_clip[1], p_ofs, view, dist);
 
-                       if (clip_segment_v3_plane_n(view_clip[0], view_clip[1], kcd->vc.rv3d->clip_local, 6)) {
+                       if (clip_segment_v3_plane_n(
+                               view_clip[0], view_clip[1], kcd->vc.rv3d->clip_local, 6,
+                               view_clip[0], view_clip[1]))
+                       {
                                dist = len_v3v3(p_ofs, view_clip[1]);
                        }
                }
 
                /* see if there's a face hit between p1 and the view */
-               f_hit = BKE_bmbvh_ray_cast(kcd->bmbvh, p_ofs, view, KNIFE_FLT_EPS, &dist, NULL, NULL);
-               if (f_hit)
+               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);
+               }
+               else {
+                       f_hit = BKE_bmbvh_ray_cast(
+                                   kcd->bmbvh, p_ofs, view, KNIFE_FLT_EPS, &dist, NULL, NULL);
+               }
+
+               if (f_hit) {
                        return false;
+               }
        }
 
        return true;
@@ -1240,14 +1463,20 @@ static bool point_is_visible(KnifeTool_OpData *kcd, const float p[3], const floa
 
 /* Clip the line (v1, v2) to planes perpendicular to it and distances d from
  * the closest point on the line to the origin */
-static void clip_to_ortho_planes(float v1[3], float v2[3], float d)
+static void clip_to_ortho_planes(float v1[3], float v2[3], const float center[3], const float d)
 {
-       float closest[3];
-       const float origin[3] = {0.0f, 0.0f, 0.0f};
+       float closest[3], dir[3];
+
+       sub_v3_v3v3(dir, v1, v2);
+       normalize_v3(dir);
 
-       closest_to_line_v3(closest, origin, v1, v2);
-       dist_ensure_v3_v3fl(v1, closest, d);
-       dist_ensure_v3_v3fl(v2, closest, d);
+       /* could be v1 or v2 */
+       sub_v3_v3(v1, center);
+       project_plane_v3_v3v3(closest, v1, dir);
+       add_v3_v3(closest, center);
+
+       madd_v3_v3v3fl(v1, closest, dir,  d);
+       madd_v3_v3v3fl(v2, closest, dir, -d);
 }
 
 static void set_linehit_depth(KnifeTool_OpData *kcd, KnifeLineHit *lh)
@@ -1274,6 +1503,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
        SmallHashIter hiter;
        KnifeLineHit hit;
        void *val;
+       void **val_p;
        float plane_cos[12];
        float s[2], se1[2], se2[2], sint[2];
        float r1[3], r2[3];
@@ -1281,10 +1511,11 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
        float vert_tol, vert_tol_sq;
        float line_tol, line_tol_sq;
        float face_tol, face_tol_sq;
-       float eps_scale;
        int isect_kind;
        unsigned int tot;
        int i;
+       const bool use_hit_prev = true;
+       const bool use_hit_curr = (kcd->is_drag_hold == false);
 
        bgl_get_mats(&mats);
 
@@ -1329,8 +1560,8 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
        if (kcd->is_ortho && (kcd->vc.rv3d->persp != RV3D_CAMOB)) {
                if (kcd->ortho_extent == 0.0f)
                        calc_ortho_extent(kcd);
-               clip_to_ortho_planes(v1, v3, kcd->ortho_extent + 10.0f);
-               clip_to_ortho_planes(v2, v4, kcd->ortho_extent + 10.0f);
+               clip_to_ortho_planes(v1, v3, kcd->ortho_extent_center, kcd->ortho_extent + 10.0f);
+               clip_to_ortho_planes(v2, v4, kcd->ortho_extent_center, kcd->ortho_extent + 10.0f);
        }
 
        /* First use bvh tree to find faces, knife edges, and knife verts that might
@@ -1346,7 +1577,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
        BLI_bvhtree_insert(planetree, 0, plane_cos, 4);
        BLI_bvhtree_balance(planetree);
 
-       results = BLI_bvhtree_overlap(tree, planetree, &tot);
+       results = BLI_bvhtree_overlap(tree, planetree, &tot, NULL, NULL);
        if (!results) {
                BLI_bvhtree_free(planetree);
                return;
@@ -1360,6 +1591,11 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
                ls = (BMLoop **)kcd->em->looptris[result->indexA];
                f = ls[0]->f;
                set_lowest_face_tri(kcd, f, result->indexA);
+
+               /* 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 */
                if (BLI_smallhash_haskey(&faces, (uintptr_t)f)) {
                        continue;
@@ -1374,27 +1610,27 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
                                continue;
                        BLI_smallhash_insert(&kfes, (uintptr_t)kfe, kfe);
                        v = kfe->v1;
-                       if (!BLI_smallhash_haskey(&kfvs, (uintptr_t)v))
-                               BLI_smallhash_insert(&kfvs, (uintptr_t)v, v);
+                       BLI_smallhash_reinsert(&kfvs, (uintptr_t)v, v);
                        v = kfe->v2;
-                       if (!BLI_smallhash_haskey(&kfvs, (uintptr_t)v))
-                               BLI_smallhash_insert(&kfvs, (uintptr_t)v, v);
+                       BLI_smallhash_reinsert(&kfvs, (uintptr_t)v, v);
                }
        }
 
        /* Now go through the candidates and find intersections */
        /* These tolerances, in screen space, are for intermediate hits, as ends are already snapped to screen */
-       {
-               /* Scale the epsilon by the zoom level
-                * to compensate for projection imprecision, see T41164 */
-               float zoom_xy[2] = {kcd->vc.rv3d->winmat[0][0],
-                                   kcd->vc.rv3d->winmat[1][1]};
-               eps_scale = len_v2(zoom_xy);
-       }
 
-       vert_tol = KNIFE_FLT_EPS_PX * eps_scale;
-       line_tol = KNIFE_FLT_EPS_PX * eps_scale;
-       face_tol = max_ff(vert_tol, line_tol);
+       if (kcd->is_interactive) {
+               vert_tol = KNIFE_FLT_EPS_PX_VERT;
+               line_tol = KNIFE_FLT_EPS_PX_EDGE;
+               face_tol = KNIFE_FLT_EPS_PX_FACE;
+       }
+       else {
+               /* Use 1/100th of a pixel, see T43896 (too big), T47910 (too small).
+                *
+                * Update, leave this as is until we investigate not using pixel coords for geometry calculations: T48023
+                */
+               vert_tol = line_tol = face_tol = 0.5f;
+       }
 
        vert_tol_sq = vert_tol * vert_tol;
        line_tol_sq = line_tol * line_tol;
@@ -1403,32 +1639,57 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
        /* Assume these tolerances swamp floating point rounding errors in calculations below */
 
        /* first look for vertex hits */
-       for (val = BLI_smallhash_iternew(&kfvs, &hiter, (uintptr_t *)&v); val;
-            val = BLI_smallhash_iternext(&hiter, (uintptr_t *)&v))
+       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;
+
                knife_project_v2(kcd, v->cageco, s);
                d = dist_squared_to_line_segment_v2(s, s1, s2);
-               if (d <= vert_tol_sq) {
-                       if (point_is_visible(kcd, v->cageco, s, &mats)) {
-                               memset(&hit, 0, sizeof(hit));
-                               hit.v = v;
-                               copy_v3_v3(hit.hit, v->co);
-                               copy_v3_v3(hit.cagehit, v->cageco);
-                               copy_v2_v2(hit.schit, s);
-                               set_linehit_depth(kcd, &hit);
-                               BLI_array_append(linehits, hit);
+               if ((d <= vert_tol_sq) &&
+                   (point_is_visible(kcd, v->cageco, s, &mats, bm_elem_from_knife_vert(v, &kfe_hit))))
+               {
+                       memset(&hit, 0, sizeof(hit));
+                       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 */
+                       if (kfe_hit) {
+                               hit.kfe = kfe_hit;
                        }
+
+                       copy_v3_v3(hit.hit, v->co);
+                       copy_v3_v3(hit.cagehit, v->cageco);
+                       copy_v2_v2(hit.schit, s);
+                       set_linehit_depth(kcd, &hit);
+                       BLI_array_append(linehits, hit);
+               }
+               else {
+                       /* note that these vertes aren't used */
+                       *val_p = NULL;
                }
        }
+
        /* 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))
        {
+               int kfe_verts_in_cut;
+               /* if we intersect both verts, don't attempt to intersect the edge */
+
+               kfe_verts_in_cut = (BLI_smallhash_lookup(&kfvs, (intptr_t)kfe->v1) != NULL) +
+                                  (BLI_smallhash_lookup(&kfvs, (intptr_t)kfe->v2) != NULL);
+
+               if (kfe_verts_in_cut == 2) {
+                       continue;
+               }
+
                knife_project_v2(kcd, kfe->v1->cageco, se1);
                knife_project_v2(kcd, kfe->v2->cageco, se2);
-               isect_kind = isect_seg_seg_v2_point(s1, s2, se1, se2, sint);
+               isect_kind = (kfe_verts_in_cut) ? -1 : isect_seg_seg_v2_point(s1, s2, se1, se2, sint);
                if (isect_kind == -1) {
-                       /* isect_seg_seg_v2 doesn't do tolerance test around ends of s1-s2 */
+                       /* isect_seg_seg_v2_simple 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;
@@ -1449,7 +1710,7 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
                                 * 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, &mats)) {
+                               if (isect_kind >= 1 && point_is_visible(kcd, p_cage, sint, &mats, bm_elem_from_knife_edge(kfe))) {
                                        memset(&hit, 0, sizeof(hit));
                                        if (kcd->snap_midpoints) {
                                                /* choose intermediate point snap too */
@@ -1477,8 +1738,8 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
        {
                float p[3], p_cage[3];
 
-               if (knife_ray_intersect_face(kcd, s1, v1, v3, f, face_tol_sq, p, p_cage)) {
-                       if (point_is_visible(kcd, p_cage, s1, &mats)) {
+               if (use_hit_prev && knife_ray_intersect_face(kcd, s1, v1, v3, f, face_tol_sq, p, p_cage)) {
+                       if (point_is_visible(kcd, p_cage, s1, &mats, (BMElem *)f)) {
                                memset(&hit, 0, sizeof(hit));
                                hit.f = f;
                                copy_v3_v3(hit.hit, p);
@@ -1488,8 +1749,9 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
                                BLI_array_append(linehits, hit);
                        }
                }
-               if (knife_ray_intersect_face(kcd, s2, v2, v4, f, face_tol_sq, p, p_cage)) {
-                       if (point_is_visible(kcd, p_cage, s2, &mats)) {
+
+               if (use_hit_curr && knife_ray_intersect_face(kcd, s2, v2, v4, f, face_tol_sq, p, p_cage)) {
+                       if (point_is_visible(kcd, p_cage, s2, &mats, (BMElem *)f)) {
                                memset(&hit, 0, sizeof(hit));
                                hit.f = f;
                                copy_v3_v3(hit.hit, p);
@@ -1543,13 +1805,18 @@ static BMFace *knife_find_closest_face(KnifeTool_OpData *kcd, float co[3], float
        float dist = KMAXDIST;
        float origin[3];
        float origin_ofs[3];
-       float ray[3];
+       float ray[3], ray_normal[3];
 
        /* 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, 0.0f, NULL, co, cageco);
+       f = BKE_bmbvh_ray_cast(kcd->bmbvh, origin, ray_normal, 0.0f, NULL, co, cageco);
+
+       if (f && kcd->only_select && BM_elem_flag_test(f, BM_ELEM_SELECT) == 0) {
+               f = NULL;
+       }
 
        if (is_space)
                *is_space = !f;
@@ -1624,17 +1891,7 @@ static int knife_sample_screen_density(KnifeTool_OpData *kcd, const float radius
  * surrounding mesh (in screen space)*/
 static float knife_snap_size(KnifeTool_OpData *kcd, float maxsize)
 {
-       float density;
-
-       if (kcd->is_interactive) {
-               density = (float)knife_sample_screen_density(kcd, maxsize * 2.0f);
-       }
-       else {
-               density = 1.0f;
-       }
-
-       if (density < 1.0f)
-               density = 1.0f;
+       float density = (float)knife_sample_screen_density(kcd, maxsize * 2.0f);
 
        return min_ff(maxsize / (density * 0.5f), maxsize);
 }
@@ -1645,10 +1902,18 @@ static KnifeEdge *knife_find_closest_edge(KnifeTool_OpData *kcd, float p[3], flo
 {
        BMFace *f;
        float co[3], cageco[3], sco[2];
-       float maxdist = knife_snap_size(kcd, kcd->ethresh);
+       float maxdist;
+
+       if (kcd->is_interactive) {
+               maxdist = knife_snap_size(kcd, kcd->ethresh);
 
-       if (kcd->ignore_vert_snapping)
-               maxdist *= 0.5f;
+               if (kcd->ignore_vert_snapping) {
+                       maxdist *= 0.5f;
+               }
+       }
+       else {
+               maxdist = KNIFE_FLT_EPS;
+       }
 
        f = knife_find_closest_face(kcd, co, cageco, NULL);
        *is_space = !f;
@@ -1768,10 +2033,18 @@ static KnifeVert *knife_find_closest_vert(KnifeTool_OpData *kcd, float p[3], flo
                                           bool *is_space)
 {
        BMFace *f;
-       float co[3], cageco[3], sco[2], maxdist = knife_snap_size(kcd, kcd->vthresh);
+       float co[3], cageco[3], sco[2];
+       float maxdist;
 
-       if (kcd->ignore_vert_snapping)
-               maxdist *= 0.5f;
+       if (kcd->is_interactive) {
+               maxdist = knife_snap_size(kcd, kcd->vthresh);
+               if (kcd->ignore_vert_snapping) {
+                       maxdist *= 0.5f;
+               }
+       }
+       else {
+               maxdist = KNIFE_FLT_EPS;
+       }
 
        f = knife_find_closest_face(kcd, co, cageco, is_space);
 
@@ -1853,42 +2126,41 @@ static KnifeVert *knife_find_closest_vert(KnifeTool_OpData *kcd, float p[3], flo
        return NULL;
 }
 
+/**
+ * Snaps a 2d vector to an angle, relative to \a v_ref.
+ */
+static float snap_v2_angle(float r[2], const float v[2], const float v_ref[2], float angle_snap)
+{
+       float m2[2][2];
+       float v_unit[2];
+       float angle, angle_delta;
+
+       BLI_ASSERT_UNIT_V2(v_ref);
+
+       normalize_v2_v2(v_unit, v);
+       angle = angle_signed_v2v2(v_unit, v_ref);
+       angle_delta = (roundf(angle / angle_snap) * angle_snap) - angle;
+       rotate_m2(m2, angle_delta);
+
+       mul_v2_m2v2(r, m2, v);
+       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)
 {
-       float dx, dy;
-       float w, abs_tan;
+       const float dvec_ref[2] = {0.0f, 1.0f};
+       float dvec[2], dvec_snap[2];
+       float snap_step = DEG2RADF(45);
 
-       dx = kcd->curr.mval[0] - kcd->prev.mval[0];
-       dy = kcd->curr.mval[1] - kcd->prev.mval[1];
-       if (dx == 0.0f && dy == 0.0f)
+       sub_v2_v2v2(dvec, kcd->curr.mval, kcd->prev.mval);
+       if (is_zero_v2(dvec)) {
                return false;
-
-       if (dx == 0.0f) {
-               kcd->angle_snapping = ANGLE_90;
-               kcd->curr.mval[0] = kcd->prev.mval[0];
        }
 
-       w = dy / dx;
-       abs_tan = fabsf(w);
-       if (abs_tan <= 0.4142f) { /* tan(22.5 degrees) = 0.4142 */
-               kcd->angle_snapping = ANGLE_0;
-               kcd->curr.mval[1] = kcd->prev.mval[1];
-       }
-       else if (abs_tan < 2.4142f) { /* tan(67.5 degrees) = 2.4142 */
-               if (w > 0) {
-                       kcd->angle_snapping = ANGLE_45;
-                       kcd->curr.mval[1] = kcd->prev.mval[1] + dx;
-               }
-               else {
-                       kcd->angle_snapping = ANGLE_135;
-                       kcd->curr.mval[1] = kcd->prev.mval[1] - dx;
-               }
-       }
-       else {
-               kcd->angle_snapping = ANGLE_90;
-               kcd->curr.mval[0] = kcd->prev.mval[0];
-       }
+       kcd->angle = snap_v2_angle(dvec_snap, dvec, dvec_ref, snap_step);
+
+       add_v2_v2v2(kcd->curr.mval, kcd->prev.mval, dvec_snap);
 
        copy_v2_v2(kcd->mval, kcd->curr.mval);
 
@@ -1904,7 +2176,7 @@ static int knife_update_active(KnifeTool_OpData *kcd)
        /* view matrix may have changed, reproject */
        knife_project_v2(kcd, kcd->prev.cage, kcd->prev.mval);
 
-       if (kcd->angle_snapping != ANGLE_FREE && kcd->mode == MODE_DRAGGING) {
+       if (kcd->angle_snapping && (kcd->mode == MODE_DRAGGING)) {
                kcd->is_angle_snapping = knife_snap_angle(kcd);
        }
        else {
@@ -1913,7 +2185,10 @@ static int knife_update_active(KnifeTool_OpData *kcd)
 
        kcd->curr.vert = knife_find_closest_vert(kcd, kcd->curr.co, kcd->curr.cage, &kcd->curr.bmface, &kcd->curr.is_space);
 
-       if (!kcd->curr.vert) {
+       if (!kcd->curr.vert &&
+           /* no edge snapping while dragging (edges are too sticky when cuts are immediate) */
+           !kcd->is_drag_hold)
+       {
                kcd->curr.edge = knife_find_closest_edge(kcd, kcd->curr.co, kcd->curr.cage,
                                                         &kcd->curr.bmface, &kcd->curr.is_space);
        }
@@ -1942,371 +2217,39 @@ static int knife_update_active(KnifeTool_OpData *kcd)
        return 1;
 }
 
-/* sort list of kverts by fraction along edge e */
-static void sort_by_frac_along(ListBase *lst, BMEdge *e)
+static int sort_verts_by_dist_cb(void *co_p, const void *cur_a_p, const void *cur_b_p)
 {
-       /* note, since we know the point is along the edge, sort from distance to v1co */
-       const float *v1co = e->v1->co;
-       Ref *cur = NULL, *prev = NULL, *next = NULL;
+       const KnifeVert *cur_a = ((const Ref *)cur_a_p)->ref;
+       const KnifeVert *cur_b = ((const Ref *)cur_b_p)->ref;
+       const float *co = co_p;
+       const float a_sq = len_squared_v3v3(co, cur_a->co);
+       const float b_sq = len_squared_v3v3(co, cur_b->co);
 
-       if (lst->first == lst->last)
-               return;
+       if      (a_sq < b_sq) return -1;
+       else if (a_sq > b_sq) return  1;
+       else                  return  0;
+}
 
-       for (cur = ((Ref *)lst->first)->next; cur; cur = next) {
-               KnifeVert *vcur = cur->ref;
-               const float vcur_fac_sq = len_squared_v3v3(v1co, vcur->co);
+static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f)
+{
+       bool v1_inside, v2_inside;
+       bool v1_inface, v2_inface;
+       BMLoop *l1, *l2;
 
-               next = cur->next;
-               prev = cur->prev;
+       if (!f || !v1 || !v2)
+               return false;
 
-               BLI_remlink(lst, cur);
+       l1 = v1->v ? BM_face_vert_share_loop(f, v1->v) : NULL;
+       l2 = v2->v ? BM_face_vert_share_loop(f, v2->v) : NULL;
 
-               while (prev) {
-                       KnifeVert *vprev = prev->ref;
-                       if (len_squared_v3v3(v1co, vprev->co) <= vcur_fac_sq)
-                               break;
-                       prev = prev->prev;
-               }
-
-               BLI_insertlinkafter(lst, prev, cur);
-       }
-}
-
-/* The chain so far goes from an instantiated vertex to kfv (some may be reversed).
- * If possible, complete the chain to another instantiated vertex and return 1, else return 0.
- * The visited hash says which KnifeVert's have already been tried, not including kfv. */
-static bool find_chain_search(KnifeTool_OpData *kcd, KnifeVert *kfv, ListBase *fedges, SmallHash *visited,
-                              ListBase *chain)
-{
-       Ref *r;
-       KnifeEdge *kfe;
-       KnifeVert *kfv_other;
-
-       if (kfv->v)
-               return true;
-
-       BLI_smallhash_insert(visited, (uintptr_t)kfv, NULL);
-       /* Try all possible next edges. Could either go through fedges
-        * (all the KnifeEdges for the face being cut) or could go through
-        * kve->edges and restrict to cutting face and uninstantiated edges.
-        * Not clear which is better. Let's do the first. */
-       for (r = fedges->first; r; r = r->next) {
-               kfe = r->ref;
-               kfv_other = NULL;
-               if (kfe->v1 == kfv)
-                       kfv_other = kfe->v2;
-               else if (kfe->v2 == kfv)
-                       kfv_other = kfe->v1;
-               if (kfv_other && !BLI_smallhash_haskey(visited, (uintptr_t)kfv_other)) {
-                       knife_append_list(kcd, chain, kfe);
-                       if (find_chain_search(kcd, kfv_other, fedges, visited, chain))
-                               return true;
-                       BLI_remlink(chain, chain->last);
-               }
-       }
-       return false;
-}
-
-static ListBase *find_chain_from_vertex(KnifeTool_OpData *kcd, KnifeEdge *kfe, BMVert *v, ListBase *fedges)
-{
-       SmallHash visited_, *visited = &visited_;
-       ListBase *ans;
-       bool found;
-
-       ans = knife_empty_list(kcd);
-       knife_append_list(kcd, ans, kfe);
-       found = false;
-       BLI_smallhash_init(visited);
-       if (kfe->v1->v == v) {
-               BLI_smallhash_insert(visited, (uintptr_t)(kfe->v1), NULL);
-               found = find_chain_search(kcd, kfe->v2, fedges, visited, ans);
-       }
-       else {
-               BLI_assert(kfe->v2->v == v);
-               BLI_smallhash_insert(visited, (uintptr_t)(kfe->v2), NULL);
-               found = find_chain_search(kcd, kfe->v1, fedges, visited, ans);
-       }
-
-       BLI_smallhash_release(visited);
-
-       if (found)
-               return ans;
-       else
-               return NULL;
-}
-
-/* Find a chain in fedges from one instantiated vertex to another.
- * Remove the edges in the chain from fedges and return a separate list of the chain. */
-static ListBase *find_chain(KnifeTool_OpData *kcd, ListBase *fedges)
-{
-       Ref *r, *ref;
-       KnifeEdge *kfe;
-       BMVert *v1, *v2;
-       ListBase *ans;
-
-       ans = NULL;
-
-       for (r = fedges->first; r; r = r->next) {
-               kfe = r->ref;
-               v1 = kfe->v1->v;
-               v2 = kfe->v2->v;
-               if (v1 && v2) {
-                       ans = knife_empty_list(kcd);
-                       knife_append_list(kcd, ans, kfe);
-                       break;
-               }
-               if (v1)
-                       ans = find_chain_from_vertex(kcd, kfe, v1, fedges);
-               else if (v2)
-                       ans = find_chain_from_vertex(kcd, kfe, v2, fedges);
-               if (ans)
-                       break;
-       }
-       if (ans) {
-               BLI_assert(BLI_countlist(ans) > 0);
-               for (r = ans->first; r; r = r->next) {
-                       ref = find_ref(fedges, r->ref);
-                       BLI_assert(ref != NULL);
-                       BLI_remlink(fedges, ref);
-               }
-       }
-       return ans;
-}
-
-/* The hole so far goes from kfvfirst to kfv (some may be reversed).
- * If possible, complete the hole back to kfvfirst and return 1, else return 0.
- * The visited hash says which KnifeVert's have already been tried, not including kfv or kfvfirst. */
-static bool find_hole_search(KnifeTool_OpData *kcd, KnifeVert *kfvfirst, KnifeVert *kfv, ListBase *fedges,
-                             SmallHash *visited, ListBase *hole)
-{
-       Ref *r;
-       KnifeEdge *kfe, *kfelast;
-       KnifeVert *kfv_other;
-
-       if (kfv == kfvfirst)
-               return true;
-
-       BLI_smallhash_insert(visited, (uintptr_t)kfv, NULL);
-       kfelast = ((Ref *)hole->last)->ref;
-       for (r = fedges->first; r; r = r->next) {
-               kfe = r->ref;
-               if (kfe == kfelast)
-                       continue;
-               if (kfe->v1->v || kfe->v2->v)
-                       continue;
-               kfv_other = NULL;
-               if (kfe->v1 == kfv)
-                       kfv_other = kfe->v2;
-               else if (kfe->v2 == kfv)
-                       kfv_other = kfe->v1;
-               if (kfv_other && !BLI_smallhash_haskey(visited, (uintptr_t)kfv_other)) {
-                       knife_append_list(kcd, hole, kfe);
-                       if (find_hole_search(kcd, kfvfirst, kfv_other, fedges, visited, hole))
-                               return true;
-                       BLI_remlink(hole, hole->last);
-               }
-       }
-       return false;
-}
-
-/* Find a hole (simple cycle with no instantiated vertices).
- * Remove the edges in the cycle from fedges and return a separate list of the cycle */
-static ListBase *find_hole(KnifeTool_OpData *kcd, ListBase *fedges)
-{
-       ListBase *ans;
-       Ref *r, *ref;
-       KnifeEdge *kfe;
-       SmallHash visited_, *visited = &visited_;
-       bool found;
-
-       ans = NULL;
-       found = false;
-
-       for (r = fedges->first; r && !found; r = r->next) {
-               kfe = r->ref;
-               if (kfe->v1->v || kfe->v2->v || kfe->v1 == kfe->v2)
-                       continue;
-
-               BLI_smallhash_init(visited);
-               ans = knife_empty_list(kcd);
-               knife_append_list(kcd, ans, kfe);
-
-               found = find_hole_search(kcd, kfe->v1, kfe->v2, fedges, visited, ans);
-
-               BLI_smallhash_release(visited);
-       }
-
-       if (found) {
-               for (r = ans->first; r; r = r->next) {
-                       kfe = r->ref;
-                       ref = find_ref(fedges, r->ref);
-                       if (ref)
-                               BLI_remlink(fedges, ref);
-               }
-               return ans;
-       }
-       else {
-               return NULL;
-       }
-}
-
-/* Try to find "nice" diagonals - short, and far apart from each other.
- * If found, return true and make a 'main chain' going across f which uses
- * the two diagonals and one part of the hole, and a 'side chain' that
- * completes the hole. */
-static bool find_hole_chains(KnifeTool_OpData *kcd, ListBase *hole, BMFace *f, ListBase **mainchain,
-                             ListBase **sidechain)
-{
-       float (*fco)[2], (*hco)[2];
-       BMVert **fv;
-       KnifeVert **hv;
-       KnifeEdge **he;
-       Ref *r;
-       KnifeVert *kfv, *kfvother;
-       KnifeEdge *kfe;
-       ListBase *chain;
-       BMVert *v;
-       BMIter iter;
-       int nh, nf, i, j, k, m, ax, ay, sep = 0 /* Quite warnings */, bestsep;
-       int besti[2], bestj[2];
-       float dist_sq, dist_best_sq;
-
-       nh = BLI_countlist(hole);
-       nf = f->len;
-       if (nh < 2 || nf < 3)
+       if ((l1 && l2) && BM_loop_is_adjacent(l1, l2)) {
+               /* boundary-case, always false to avoid edge-in-face checks below */
                return false;
-
-       /* Gather 2d projections of hole and face vertex coordinates.
-        * Use best-axis projection - not completely accurate, maybe revisit */
-       axis_dominant_v3(&ax, &ay, f->no);
-       hco = BLI_memarena_alloc(kcd->arena, nh * sizeof(float[2]));
-       fco = BLI_memarena_alloc(kcd->arena, nf * sizeof(float[2]));
-       hv = BLI_memarena_alloc(kcd->arena, nh * sizeof(KnifeVert *));
-       fv = BLI_memarena_alloc(kcd->arena, nf * sizeof(BMVert *));
-       he = BLI_memarena_alloc(kcd->arena, nh * sizeof(KnifeEdge *));
-
-       i = 0;
-       kfv = NULL;
-       kfvother = NULL;
-       for (r = hole->first; r; r = r->next) {
-               kfe = r->ref;
-               he[i] = kfe;
-               if (kfvother == NULL) {
-                       kfv = kfe->v1;
-               }
-               else {
-                       kfv = kfvother;
-                       BLI_assert(kfv == kfe->v1 || kfv == kfe->v2);
-               }
-               hco[i][0] = kfv->co[ax];
-               hco[i][1] = kfv->co[ay];
-               hv[i] = kfv;
-               kfvother = (kfe->v1 == kfv) ? kfe->v2 : kfe->v1;
-               i++;
-       }
-
-       j = 0;
-       BM_ITER_ELEM (v, &iter, f, BM_VERTS_OF_FACE) {
-               fco[j][0] = v->co[ax];
-               fco[j][1] = v->co[ay];
-               fv[j] = v;
-               j++;
-       }
-
-       /* For first diagonal  (m == 0), want shortest length.
-        * For second diagonal (m == 1), want max separation of index of hole
-        * vertex from the hole vertex used in the first diagonal, and from there
-        * want the one with shortest length not to the same vertex as the first diagonal. */
-       for (m = 0; m < 2; m++) {
-               besti[m] = -1;
-               bestj[m] = -1;
-               dist_best_sq = FLT_MAX;
-               bestsep = 0;
-               for (i = 0; i < nh; i++) {
-                       if (m == 1) {
-                               if (i == besti[0])
-                                       continue;
-                               sep = (i + nh - besti[0]) % nh;
-                               sep = MIN2(sep, nh - sep);
-                               if (sep < bestsep)
-                                       continue;
-                               dist_best_sq = FLT_MAX;
-                       }
-                       for (j = 0; j < nf; j++) {
-                               bool ok;
-
-                               if (m == 1 && j == bestj[0])
-                                       continue;
-                               dist_sq = len_squared_v2v2(hco[i], fco[j]);
-                               if (dist_sq > dist_best_sq)
-                                       continue;
-
-                               ok = true;
-                               for (k = 0; k < nh && ok; k++) {
-                                       if (k == i || (k + 1) % nh == i)
-                                               continue;
-                                       if (isect_line_line_v2(hco[i], fco[j], hco[k], hco[(k + 1) % nh]))
-                                               ok = false;
-                               }
-                               if (!ok)
-                                       continue;
-                               for (k = 0; k < nf && ok; k++) {
-                                       if (k == j || (k + 1) % nf == j)
-                                               continue;
-                                       if (isect_line_line_v2(hco[i], fco[j], fco[k], fco[(k + 1) % nf]))
-                                               ok = false;
-                               }
-                               if (ok) {
-                                       besti[m] = i;
-                                       bestj[m] = j;
-                                       if (m == 1)
-                                               bestsep = sep;
-                                       dist_best_sq = dist_sq;
-                               }
-                       }
-               }
        }
 
-       if (besti[0] != -1 && besti[1] != -1) {
-               BLI_assert(besti[0] != besti[1] && bestj[0] != bestj[1]);
-               kfe = new_knife_edge(kcd);
-               kfe->v1 = get_bm_knife_vert(kcd, fv[bestj[0]]);
-               kfe->v2 = hv[besti[0]];
-               chain = knife_empty_list(kcd);
-               knife_append_list(kcd, chain, kfe);
-               for (i = besti[0]; i != besti[1]; i = (i + 1) % nh) {
-                       knife_append_list(kcd, chain, he[i]);
-               }
-               kfe = new_knife_edge(kcd);
-               kfe->v1 = hv[besti[1]];
-               kfe->v2 = get_bm_knife_vert(kcd, fv[bestj[1]]);
-               knife_append_list(kcd, chain, kfe);
-               *mainchain = chain;
-
-               chain = knife_empty_list(kcd);
-               for (i = besti[1]; i != besti[0]; i = (i + 1) % nh) {
-                       knife_append_list(kcd, chain, he[i]);
-               }
-               *sidechain = chain;
-
-               return true;
-       }
-       else {
-               return false;
-       }
-}
-
-static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f)
-{
-       bool v1_inside, v2_inside;
-       bool v1_inface, v2_inface;
-
-       if (!f || !v1 || !v2)
-               return false;
-
        /* find out if v1 and v2, if set, are part of the face */
-       v1_inface = v1->v ? BM_vert_in_face(f, v1->v) : false;
-       v2_inface = v2->v ? BM_vert_in_face(f, v2->v) : false;
+       v1_inface = (l1 != NULL);
+       v2_inface = (l2 != NULL);
 
        /* BM_face_point_inside_test uses best-axis projection so this isn't most accurate test... */
        v1_inside = v1_inface ? false : BM_face_point_inside_test(f, v1->co);
@@ -2330,202 +2273,119 @@ static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f)
        return false;
 }
 
-static bool knife_edge_in_face(KnifeEdge *kfe, BMFace *f)
-{
-       return knife_verts_edge_in_face(kfe->v1, kfe->v2, f);
-}
-
-/* Split face f with KnifeEdges on chain.  f remains as one side, the face formed is put in *newface.
- * The new face will be on the left side of the chain as viewed from the normal-out side of f. */
-static void knife_make_chain_cut(KnifeTool_OpData *kcd, BMFace *f, ListBase *chain, BMFace **r_f_new)
+static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfedges)
 {
        BMesh *bm = kcd->em->bm;
-       KnifeEdge *kfe, *kfelast;
-       BMVert *v1, *v2;
-       BMLoop *l_v1, *l_v2;
-       BMFace *f_new;
+       KnifeEdge *kfe;
        Ref *ref;
-       KnifeVert *kfv, *kfvprev;
-       BMLoop *l_new, *l_iter;
+       int edge_array_len = BLI_listbase_count(kfedges);
        int i;
-       int nco = BLI_countlist(chain) - 1;
-       float (*cos)[3] = BLI_array_alloca(cos, nco);
-       KnifeVert **kverts = BLI_array_alloca(kverts, nco);
-
-       kfe = ((Ref *)chain->first)->ref;
-       v1 = kfe->v1->v ? kfe->v1->v : kfe->v2->v;
-       kfelast = ((Ref *)chain->last)->ref;
-       v2 = kfelast->v2->v ? kfelast->v2->v : kfelast->v1->v;
-       BLI_assert(v1 != NULL && v2 != NULL);
-       kfvprev = kfe->v1->v == v1 ? kfe->v1 : kfe->v2;
-       for (ref = chain->first, i = 0; i < nco && ref != chain->last; ref = ref->next, i++) {
+
+       BMEdge **edge_array = BLI_array_alloca(edge_array, edge_array_len);
+
+       /* 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_size(kcd->edgenet.edge_visit) == 0);
+
+       i = 0;
+       for (ref = kfedges->first; ref; ref = ref->next) {
+               bool is_new_edge = false;
                kfe = ref->ref;
-               BLI_assert(kfvprev == kfe->v1 || kfvprev == kfe->v2);
-               kfv = kfe->v1 == kfvprev ? kfe->v2 : kfe->v1;
-               copy_v3_v3(cos[i], kfv->co);
-               kverts[i] = kfv;
-               kfvprev = kfv;
-       }
-       BLI_assert(i == nco);
-       l_new = NULL;
 
-       if ((l_v1 = BM_face_vert_share_loop(f, v1)) &&
-           (l_v2 = BM_face_vert_share_loop(f, v2)))
-       {
-               if (nco == 0) {
-                       /* Want to prevent creating two-sided polygons */
-                       if (v1 == v2 || BM_edge_exists(v1, v2)) {
-                               f_new = NULL;
+               if (kfe->e == NULL) {
+                       if (kfe->v1->v && kfe->v2->v) {
+                               kfe->e = BM_edge_exists(kfe->v1->v, kfe->v2->v);
                        }
-                       else {
-                               f_new = BM_face_split(bm, f, l_v1, l_v2, &l_new, NULL, true);
+               }
+
+               if (kfe->e) {
+                       if (BM_edge_in_face(kfe->e, f)) {
+                               /* shouldn't happen, but in this case - just ignore */
+                               continue;
                        }
                }
                else {
-                       f_new = BM_face_split_n(bm, f, l_v1, l_v2, cos, nco, &l_new, NULL);
-                       if (f_new) {
-                               /* Now go through lnew chain matching up chain kv's and assign real v's to them */
-                               for (l_iter = l_new->next, i = 0; i < nco; l_iter = l_iter->next, i++) {
-                                       BLI_assert(equals_v3v3(cos[i], l_iter->v->co));
-                                       if (kcd->select_result) {
-                                               BM_edge_select_set(bm, l_iter->e, true);
-                                       }
-                                       kverts[i]->v = l_iter->v;
+                       if (kfe->v1->v == NULL) {
+                               kfe->v1->v = BM_vert_create(bm, kfe->v1->co, NULL, 0);
+                       }
+                       if (kfe->v2->v == NULL) {
+                               kfe->v2->v = BM_vert_create(bm, kfe->v2->co, NULL, 0);
+                       }
+                       BLI_assert(kfe->e == NULL);
+                       kfe->e = BM_edge_create(bm, kfe->v1->v, kfe->v2->v, NULL, 0);
+                       if (kfe->e) {
+                               if (kcd->select_result || BM_elem_flag_test(f, BM_ELEM_SELECT)) {
+                                       BM_edge_select_set(bm, kfe->e, true);
                                }
+                               is_new_edge = true;
                        }
                }
-       }
-       else {
-               f_new = NULL;
-       }
 
-       /* the select chain above doesnt account for the first loop */
-       if (kcd->select_result) {
-               if (l_new) {
-                       BM_edge_select_set(bm, l_new->e, true);
+               BLI_assert(kfe->e);
+
+               if (BLI_gset_add(kcd->edgenet.edge_visit, kfe->e)) {
+                       kfe_array[i] = is_new_edge ? kfe : 0;
+                       edge_array[i] = kfe->e;
+                       i += 1;
                }
        }
-       else if (f_new) {
-               BM_elem_select_copy(bm, bm, f_new, f);
-       }
-
-       *r_f_new = f_new;
-}
 
-static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfedges)
-{
-       BMesh *bm = kcd->em->bm;
-       KnifeEdge *kfe;
-       BMFace *fnew, *fnew2, *fhole;
-       ListBase *chain, *hole, *sidechain;
-       Ref *ref, *refnext;
-       int count, oldcount;
-
-       oldcount = BLI_countlist(kfedges);
-       while ((chain = find_chain(kcd, kfedges)) != NULL) {
-               ListBase fnew_kfedges;
-               knife_make_chain_cut(kcd, f, chain, &fnew);
-               if (!fnew) {
-                       return;
-               }
+       if (i) {
+               const int edge_array_len_orig = i;
+               edge_array_len = i;
 
-               /* Move kfedges to fnew_kfedges if they are now in fnew.
-                * The chain edges were removed already */
-               BLI_listbase_clear(&fnew_kfedges);
-               for (ref = kfedges->first; ref; ref = refnext) {
-                       kfe = ref->ref;
-                       refnext = ref->next;
-                       if (knife_edge_in_face(kfe, fnew)) {
-                               BLI_remlink(kfedges, ref);
-                               kfe->basef = fnew;
-                               BLI_addtail(&fnew_kfedges, ref);
-                       }
-                       else if (!knife_edge_in_face(kfe, f)) {
-                               /* Concave ngon's - this edge might not be in either faces, T41730 */
-                               BLI_remlink(kfedges, ref);
+#ifdef USE_NET_ISLAND_CONNECT
+               unsigned int edge_array_holes_len;
+               BMEdge **edge_array_holes;
+               if (BM_face_split_edgenet_connect_islands(
+                       bm, f,
+                       edge_array, edge_array_len,
+                       true,
+                       kcd->edgenet.arena,
+                       &edge_array_holes, &edge_array_holes_len))
+               {
+                       if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
+                               for (i = edge_array_len; i < edge_array_holes_len; i++) {
+                                       BM_edge_select_set(bm, edge_array_holes[i], true);
+                               }
                        }
-               }
-               if (fnew_kfedges.first)
-                       knife_make_face_cuts(kcd, fnew, &fnew_kfedges);
 
-               /* find_chain should always remove edges if it returns true,
-                * but guard against infinite loop anyway */
-               count = BLI_countlist(kfedges);
-               if (count >= oldcount) {
-                       BLI_assert(!"knife find_chain infinite loop");
-                       return;
+                       edge_array_len = edge_array_holes_len;
+                       edge_array = edge_array_holes;  /* owned by the arena */
                }
-               oldcount = count;
-       }
+#endif
 
-       while ((hole = find_hole(kcd, kfedges)) != NULL) {
-               if (find_hole_chains(kcd, hole, f, &chain, &sidechain)) {
-                       ListBase fnew_kfedges, fnew2_kfedges;
+               {
+                       BMFace **face_arr = NULL;
+                       int face_arr_len;
 
-                       /* chain goes across f and sidechain comes back
-                        * from the second last vertex to the second vertex.
-                        */
-                       knife_make_chain_cut(kcd, f, chain, &fnew);
-                       if (!fnew) {
-                               BLI_assert(!"knife failed hole cut");
-                               return;
-                       }
-                       kfe = ((Ref *)sidechain->first)->ref;
-                       if (knife_edge_in_face(kfe, f)) {
-                               knife_make_chain_cut(kcd, f, sidechain, &fnew2);
-                               if (fnew2 == NULL) {
-                                       return;
-                               }
-                               fhole = f;
-                       }
-                       else if (knife_edge_in_face(kfe, fnew)) {
-                               knife_make_chain_cut(kcd, fnew, sidechain, &fnew2);
-                               if (fnew2 == NULL) {
-                                       return;
-                               }
-                               fhole = fnew2;
-                       }
-                       else {
-                               /* shouldn't happen except in funny edge cases */
-                               return;
+                       BM_face_split_edgenet(
+                               bm, f, edge_array, edge_array_len,
+                               &face_arr, &face_arr_len);
+
+                       if (face_arr) {
+                               MEM_freeN(face_arr);
                        }
-                       BM_face_kill(bm, fhole);
-                       /* Move kfedges to either fnew or fnew2 if appropriate.
-                        * The hole edges were removed already */
-                       BLI_listbase_clear(&fnew_kfedges);
-                       BLI_listbase_clear(&fnew2_kfedges);
-                       for (ref = kfedges->first; ref; ref = refnext) {
-                               kfe = ref->ref;
-                               refnext = ref->next;
-                               if (knife_edge_in_face(kfe, fnew)) {
-                                       BLI_remlink(kfedges, ref);
-                                       kfe->basef = fnew;
-                                       BLI_addtail(&fnew_kfedges, ref);
-                               }
-                               else if (knife_edge_in_face(kfe, fnew2)) {
-                                       BLI_remlink(kfedges, ref);
-                                       kfe->basef = fnew2;
-                                       BLI_addtail(&fnew2_kfedges, ref);
+               }
+
+               /* 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)) {
+                                       BM_edge_kill(bm, kfe_array[i]->e);
+                                       kfe_array[i]->e = NULL;
                                }
                        }
-                       /* We'll skip knife edges that are in the newly formed hole.
-                        * (Maybe we shouldn't have made a hole in the first place?) */
-                       if (fnew != fhole && fnew_kfedges.first)
-                               knife_make_face_cuts(kcd, fnew, &fnew_kfedges);
-                       if (fnew2 != fhole && fnew2_kfedges.first)
-                               knife_make_face_cuts(kcd, fnew2, &fnew2_kfedges);
-                       if (f == fhole)
-                               break;
-                       /* find_hole should always remove edges if it returns true,
-                        * but guard against infinite loop anyway */
-                       count = BLI_countlist(kfedges);
-                       if (count >= oldcount) {
-                               BLI_assert(!"knife find_hole infinite loop");
-                               return;
-                       }
-                       oldcount = count;
                }
+
+
+#ifdef USE_NET_ISLAND_CONNECT
+               BLI_memarena_clear(kcd->edgenet.arena);
+#endif
        }
+
+       BLI_gset_clear(kcd->edgenet.edge_visit, NULL);
 }
 
 /* Use the network of KnifeEdges and KnifeVerts accumulated to make real BMVerts and BMEdedges */
@@ -2550,6 +2410,14 @@ static void knife_make_cuts(KnifeTool_OpData *kcd)
        /* 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)) {
+
+               /* 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);
+                       }
+               }
+
                f = kfe->basef;
                if (!f || kfe->e)
                        continue;
@@ -2586,7 +2454,8 @@ static void knife_make_cuts(KnifeTool_OpData *kcd)
        for (lst = BLI_smallhash_iternew(ehash, &hiter, (uintptr_t *)&e); lst;
             lst = BLI_smallhash_iternext(&hiter, (uintptr_t *)&e))
        {
-               sort_by_frac_along(lst, e);
+               BLI_listbase_sort_r(lst, sort_verts_by_dist_cb, e->v1->co);
+
                for (ref = lst->first; ref; ref = ref->next) {
                        kfv = ref->ref;
                        pct = line_point_factor_v3(kfv->co, e->v1->co, e->v2->co);
@@ -2617,6 +2486,9 @@ static void knifetool_finish_ex(KnifeTool_OpData *kcd)
        EDBM_selectmode_flush(kcd->em);
        EDBM_mesh_normals_update(kcd->em);
        EDBM_update_generic(kcd->em, true, true);
+
+       /* re-tessellating makes this invalid, dont use again by accident */
+       knifetool_free_bmbvh(kcd);
 }
 static void knifetool_finish(wmOperator *op)
 {
@@ -2630,8 +2502,7 @@ static void knife_recalc_projmat(KnifeTool_OpData *kcd)
        ED_view3d_ob_project_mat_get(kcd->ar->regiondata, kcd->ob, kcd->projmat);
        invert_m4_m4(kcd->projmat_inv, kcd->projmat);
 
-       copy_v3_v3(kcd->proj_zaxis, kcd->vc.rv3d->viewinv[2]);
-       mul_mat3_m4_v3(kcd->ob->imat, kcd->proj_zaxis);
+       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.v3d, kcd->vc.rv3d,
@@ -2661,14 +2532,16 @@ static void knifetool_exit_ex(bContext *C, KnifeTool_OpData *kcd)
        BLI_ghash_free(kcd->kedgefacemap, NULL, NULL);
        BLI_ghash_free(kcd->facetrimap, NULL, NULL);
 
-       BKE_bmbvh_free(kcd->bmbvh);
        BLI_memarena_free(kcd->arena);
+#ifdef USE_NET_ISLAND_CONNECT
+       BLI_memarena_free(kcd->edgenet.arena);
+#endif
+       BLI_gset_free(kcd->edgenet.edge_visit, NULL);
 
        /* tag for redraw */
        ED_region_tag_redraw(kcd->ar);
 
-       if (kcd->cagecos)
-               MEM_freeN((void *)kcd->cagecos);
+       knifetool_free_bmbvh(kcd);
 
        if (kcd->linehits)
                MEM_freeN(kcd->linehits);
@@ -2699,6 +2572,32 @@ static void knifetool_update_mval_i(KnifeTool_OpData *kcd, const int mval_i[2])
        knifetool_update_mval(kcd, mval);
 }
 
+static void knifetool_init_bmbvh(KnifeTool_OpData *kcd)
+{
+       BM_mesh_elem_index_ensure(kcd->em->bm, BM_VERT);
+
+       kcd->cagecos = (const float (*)[3])BKE_editmesh_vertexCos_get(kcd->em, kcd->scene, NULL);
+
+       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);
+}
+
+static void knifetool_free_bmbvh(KnifeTool_OpData *kcd)
+{
+       if (kcd->bmbvh) {
+               BKE_bmbvh_free(kcd->bmbvh);
+               kcd->bmbvh = NULL;
+       }
+
+       if (kcd->cagecos) {
+               MEM_freeN((void *)kcd->cagecos);
+               kcd->cagecos = NULL;
+       }
+}
+
 /* called when modal loop selection gets set up... */
 static void knifetool_init(bContext *C, KnifeTool_OpData *kcd,
                            const bool only_select, const bool cut_through, const bool is_interactive)
@@ -2707,6 +2606,7 @@ static void knifetool_init(bContext *C, KnifeTool_OpData *kcd,
        Object *obedit = CTX_data_edit_object(C);
 
        /* assign the drawing handle for drawing preview line... */
+       kcd->scene = scene;
        kcd->ob = obedit;
        kcd->ar = CTX_wm_region(C);
 
@@ -2714,16 +2614,19 @@ static void knifetool_init(bContext *C, KnifeTool_OpData *kcd,
 
        kcd->em = BKE_editmesh_from_object(kcd->ob);
 
-       BM_mesh_elem_index_ensure(kcd->em->bm, BM_VERT);
-
-       kcd->cagecos = (const float (*)[3])BKE_editmesh_vertexCos_get(kcd->em, scene, NULL);
+       /* 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;
 
-       kcd->bmbvh = BKE_bmbvh_new_from_editmesh(kcd->em,
-                                                BMBVH_RETURN_ORIG |
-                                                (only_select ? BMBVH_RESPECT_SELECT : BMBVH_RESPECT_HIDDEN),
-                                                kcd->cagecos, false);
+       knifetool_init_bmbvh(kcd);
 
        kcd->arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 15), "knife");
+#ifdef USE_NET_ISLAND_CONNECT
+       kcd->edgenet.arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 15), __func__);
+#endif
+       kcd->edgenet.edge_visit = BLI_gset_ptr_new(__func__);
+
        kcd->vthresh = KMAXDIST - 1;
        kcd->ethresh = KMAXDIST;
 
@@ -2740,11 +2643,6 @@ static void knifetool_init(bContext *C, KnifeTool_OpData *kcd,
        kcd->kedgefacemap = BLI_ghash_ptr_new("knife kedgefacemap");
        kcd->facetrimap = BLI_ghash_ptr_new("knife facetrimap");
 
-       /* 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;
-
        /* can't usefully select resulting edges in face mode */
        kcd->select_result = (kcd->em->selectmode != SCE_SELECT_FACE);
 
@@ -2787,31 +2685,19 @@ static int knifetool_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 
        knifetool_init(C, kcd, only_select, cut_through, true);
 
+       op->flag |= OP_IS_MODAL_CURSOR_REGION;
+
        /* add a modal handler for this operator - handles loop selection */
        WM_cursor_modal_set(CTX_wm_window(C), BC_KNIFECURSOR);
        WM_event_add_modal_handler(C, op);
 
        knifetool_update_mval_i(kcd, event->mval);
 
-       knife_update_header(C, kcd);
+       knife_update_header(C, op, kcd);
 
        return OPERATOR_RUNNING_MODAL;
 }
 
-enum {
-       KNF_MODAL_CANCEL = 1,
-       KNF_MODAL_CONFIRM,
-       KNF_MODAL_MIDPOINT_ON,
-       KNF_MODAL_MIDPOINT_OFF,
-       KNF_MODAL_NEW_CUT,
-       KNF_MODEL_IGNORE_SNAP_ON,
-       KNF_MODEL_IGNORE_SNAP_OFF,
-       KNF_MODAL_ADD_CUT,
-       KNF_MODAL_ANGLE_SNAP_TOGGLE,
-       KNF_MODAL_CUT_THROUGH_TOGGLE,
-       KNF_MODAL_PANNING
-};
-
 wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf)
 {
        static EnumPropertyItem modal_items[] = {
@@ -2840,7 +2726,8 @@ wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf)
        /* items for modal map */
        WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, KNF_MODAL_CANCEL);
        WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_ANY, KM_ANY, 0, KNF_MODAL_PANNING);
-       WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, KNF_MODAL_ADD_CUT);
+       WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_DBL_CLICK, KM_ANY, 0, KNF_MODAL_ADD_CUT_CLOSED);
+       WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_ANY, KM_ANY, 0, KNF_MODAL_ADD_CUT);
        WM_modalkeymap_add_item(keymap, RIGHTMOUSE, KM_PRESS, KM_ANY, 0, KNF_MODAL_CANCEL);
        WM_modalkeymap_add_item(keymap, RETKEY, KM_PRESS, KM_ANY, 0, KNF_MODAL_CONFIRM);
        WM_modalkeymap_add_item(keymap, PADENTER, KM_PRESS, KM_ANY, 0, KNF_MODAL_CONFIRM);
@@ -2877,6 +2764,9 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
                return OPERATOR_FINISHED;
        }
 
+       em_setup_viewcontext(C, &kcd->vc);
+       kcd->ar = kcd->vc.ar;
+
        view3d_operator_needs_opengl(C);
        ED_view3d_init_mats_rv3d(obedit, kcd->vc.rv3d);  /* needed to initialize clipping */
 
@@ -2908,7 +2798,7 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
 
                                knife_recalc_projmat(kcd);
                                knife_update_active(kcd);
-                               knife_update_header(C, kcd);
+                               knife_update_header(C, op, kcd);
                                ED_region_tag_redraw(kcd->ar);
                                do_refresh = true;
                                break;
@@ -2917,30 +2807,30 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
 
                                knife_recalc_projmat(kcd);
                                knife_update_active(kcd);
-                               knife_update_header(C, kcd);
+                               knife_update_header(C, op, kcd);
                                ED_region_tag_redraw(kcd->ar);
                                do_refresh = true;
                                break;
                        case KNF_MODEL_IGNORE_SNAP_ON:
                                ED_region_tag_redraw(kcd->ar);
                                kcd->ignore_vert_snapping = kcd->ignore_edge_snapping = true;
-                               knife_update_header(C, kcd);
+                               knife_update_header(C, op, kcd);
                                do_refresh = true;
                                break;
                        case KNF_MODEL_IGNORE_SNAP_OFF:
                                ED_region_tag_redraw(kcd->ar);
                                kcd->ignore_vert_snapping = kcd->ignore_edge_snapping = false;
-                               knife_update_header(C, kcd);
+                               knife_update_header(C, op, kcd);
                                do_refresh = true;
                                break;
                        case KNF_MODAL_ANGLE_SNAP_TOGGLE:
                                kcd->angle_snapping = !kcd->angle_snapping;
-                               knife_update_header(C, kcd);
+                               knife_update_header(C, op, kcd);
                                do_refresh = true;
                                break;
                        case KNF_MODAL_CUT_THROUGH_TOGGLE:
                                kcd->cut_through = !kcd->cut_through;
-                               knife_update_header(C, kcd);
+                               knife_update_header(C, op, kcd);
                                do_refresh = true;
                                break;
                        case KNF_MODAL_NEW_CUT:
@@ -2951,16 +2841,53 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
                        case KNF_MODAL_ADD_CUT:
                                knife_recalc_projmat(kcd);
 
-                               if (kcd->mode == MODE_DRAGGING) {
-                                       knife_add_cut(kcd);
+                               /* get the value of the event which triggered this one */
+                               if (event->prevval != KM_RELEASE) {
+                                       if (kcd->mode == MODE_DRAGGING) {
+                                               knife_add_cut(kcd);
+                                       }
+                                       else if (kcd->mode != MODE_PANNING) {
+                                               knife_start_cut(kcd);
+                                               kcd->mode = MODE_DRAGGING;
+                                               kcd->init = kcd->curr;
+                                       }
+
+                                       /* freehand drawing is incompatible with cut-through */
+                                       if (kcd->cut_through == false) {
+                                               kcd->is_drag_hold = true;
+                                       }
                                }
-                               else if (kcd->mode != MODE_PANNING) {
-                                       knife_start_cut(kcd);
-                                       kcd->mode = MODE_DRAGGING;
+                               else {
+                                       kcd->is_drag_hold = false;
+
+                                       /* needed because the last face 'hit' is ignored when dragging */
+                                       knifetool_update_mval(kcd, kcd->curr.mval);
                                }
 
                                ED_region_tag_redraw(kcd->ar);
                                break;
+                       case KNF_MODAL_ADD_CUT_CLOSED:
+                               if (kcd->mode == MODE_DRAGGING) {
+
+                                       /* shouldn't be possible with default key-layout, just incase... */
+                                       if (kcd->is_drag_hold) {
+                                               kcd->is_drag_hold = false;
+                                               knifetool_update_mval(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);
+
+                                       knife_add_cut(kcd);
+
+                                       /* KNF_MODAL_NEW_CUT */
+                                       knife_finish_cut(kcd);
+                                       kcd->mode = MODE_IDLE;
+                               }
+                               break;
                        case KNF_MODAL_PANNING:
                                if (event->val != KM_RELEASE) {
                                        if (kcd->mode != MODE_PANNING) {
@@ -2987,12 +2914,25 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
                        case MOUSEMOVE: /* mouse moved somewhere to select another loop */
                                if (kcd->mode != MODE_PANNING) {
                                        knifetool_update_mval_i(kcd, event->mval);
+
+                                       if (kcd->is_drag_hold) {
+                                               if (kcd->totlinehit >= 2) {
+                                                       knife_add_cut(kcd);
+                                               }
+                                       }
                                }
 
                                break;
                }
        }
 
+       if (kcd->mode == MODE_DRAGGING) {
+               op->flag &= ~OP_IS_MODAL_CURSOR_REGION;
+       }
+       else {
+               op->flag |= OP_IS_MODAL_CURSOR_REGION;
+       }
+
        if (do_refresh) {
                /* we don't really need to update mval,
                 * but this happens to be the best way to refresh at the moment */
@@ -3028,77 +2968,21 @@ void MESH_OT_knife_tool(wmOperatorType *ot)
 /* Knife tool as a utility function
  * that can be used for internal slicing operations */
 
-/**
- * Return a point inside the face.
- *
- * tessellation here seems way overkill,
- * but without this its very hard to know of a point is inside the face
- */
-static void edvm_mesh_knife_face_point(BMFace *f, float r_cent[3])
+static bool edbm_mesh_knife_point_isect(LinkNode *polys, const float cent_ss[2])
 {
-       const int tottri = f->len - 2;
-       BMLoop **loops = BLI_array_alloca(loops, f->len);
-       unsigned int  (*index)[3] = BLI_array_alloca(index, tottri);
-       int j;
-
-       const float *best_co[3] = {NULL};
-       float best_area  = -1.0f;
-       bool ok = false;
-
-       BM_face_calc_tessellation(f, loops, index);
-
-       for (j = 0; j < tottri; j++) {
-               const float *p1 = loops[index[j][0]]->v->co;
-               const float *p2 = loops[index[j][1]]->v->co;
-               const float *p3 = loops[index[j][2]]->v->co;
-               float area;
-
-               float cross[3];
-               cross_v3_v3v3(cross, p2, p3);
-               area = fabsf(dot_v3v3(p1, cross));
-               if (area > best_area) {
-                       best_co[0] = p1;
-                       best_co[1] = p2;
-                       best_co[2] = p3;
-                       best_area = area;
-                       ok = true;
-               }
-       }
+       LinkNode *p = polys;
+       int isect = 0;
 
-       if (ok) {
-               mid_v3_v3v3v3(r_cent, best_co[0], best_co[1], best_co[2]);
-       }
-       else {
-               mid_v3_v3v3v3(r_cent, loops[0]->v->co, loops[1]->v->co, loops[2]->v->co);
+       while (p) {
+               const float (*mval_fl)[2] = p->link;
+               const int mval_tot = MEM_allocN_len(mval_fl) / sizeof(*mval_fl);
+               isect += (int)isect_point_poly_v2(cent_ss, mval_fl, mval_tot - 1, false);
+               p = p->next;
        }
-}
 
-static bool edbm_mesh_knife_face_isect(ARegion *ar, LinkNode *polys, BMFace *f, float projmat[4][4])
-{
-       float cent_ss[2];
-       float cent[3];
-
-       edvm_mesh_knife_face_point(f, cent);
-
-       ED_view3d_project_float_v2_m4(ar, cent, cent_ss, projmat);
-
-       /* check */
-       {
-               LinkNode *p = polys;
-               int isect = 0;
-
-               while (p) {
-                       const float (*mval_fl)[2] = p->link;
-                       const int mval_tot = MEM_allocN_len(mval_fl) / sizeof(*mval_fl);
-                       isect += (int)isect_point_poly_v2(cent_ss, mval_fl, mval_tot - 1, false);
-                       p = p->next;
-               }
-
-               if (isect % 2) {
-                       return true;
-               }
+       if (isect % 2) {
+               return true;
        }
-
        return false;
 }
 
@@ -3108,6 +2992,7 @@ static bool edbm_mesh_knife_face_isect(ARegion *ar, LinkNode *polys, BMFace *f,
 void EDBM_mesh_knife(bContext *C, LinkNode *polys, bool use_tag, bool cut_through)
 {
        KnifeTool_OpData *kcd;
+       bglMats mats;
 
        view3d_operator_needs_opengl(C);
 
@@ -3126,6 +3011,10 @@ void EDBM_mesh_knife(bContext *C, LinkNode *polys, bool use_tag, bool cut_throug
                if (use_tag) {
                        BM_mesh_elem_hflag_enable_all(kcd->em->bm, BM_EDGE, BM_ELEM_TAG, false);
                }
+
+               if (kcd->cut_through == false) {
+                       bgl_get_mats(&mats);
+               }
        }
 
        /* execute */
@@ -3169,6 +3058,11 @@ void EDBM_mesh_knife(bContext *C, LinkNode *polys, bool use_tag, bool cut_throug
 
                        bool keep_search;
 
+                       /* freed on knifetool_finish_ex, but we need again to check if points are visible */
+                       if (kcd->cut_through == false) {
+                               knifetool_init_bmbvh(kcd);
+                       }
+
                        ED_view3d_ob_project_mat_get(kcd->ar->regiondata, kcd->ob, projmat);
 
                        /* use face-loop tag to store if we have intersected */
@@ -3190,7 +3084,10 @@ void EDBM_mesh_knife(bContext *C, LinkNode *polys, bool use_tag, bool cut_throug
                                        BMFace *f;
                                        BMIter fiter;
                                        BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
-                                               if (edbm_mesh_knife_face_isect(kcd->ar, polys, f, projmat)) {
+                                               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);
                                                }
                                        }
@@ -3223,7 +3120,12 @@ void EDBM_mesh_knife(bContext *C, LinkNode *polys, bool use_tag, bool cut_throug
                                                } while ((l_iter = l_iter->next) != l_first && (found == false));
 
                                                if (found) {
-                                                       if (edbm_mesh_knife_face_isect(kcd->ar, polys, f, projmat)) {
+                                                       float cent[3], cent_ss[2];
+                                                       BM_face_calc_point_in_face(f, cent);
+                                                       knife_project_v2(kcd, cent, cent_ss);
+                                                       if ((kcd->cut_through || point_is_visible(kcd, cent, cent_ss, &mats, (BMElem *)f)) &&
+                                                           edbm_mesh_knife_point_isect(polys, cent_ss))
+                                                       {
                                                                BM_elem_flag_enable(f, BM_ELEM_TAG);
                                                                keep_search = true;
                                                        }