Fix T41164: Knife creates duplicate verts
[blender-staging.git] / source / blender / editors / mesh / editmesh_knife.c
index 2e73cbd02ed7e5815bc37e7f2287d511d1412f28..7421acac9b592f6f1cd6e4e8bffb3b874c904ea8 100644 (file)
@@ -50,6 +50,7 @@
 #include "BKE_context.h"
 #include "BKE_editmesh.h"
 #include "BKE_editmesh_bvh.h"
+#include "BKE_report.h"
 
 #include "BIF_gl.h"
 #include "BIF_glutil.h" /* for paint cursor */
@@ -76,6 +77,8 @@
 
 #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
 
 typedef struct KnifeColors {
        unsigned char line[3];
@@ -111,16 +114,20 @@ typedef struct KnifeEdge {
        bool draw;
 } KnifeEdge;
 
-typedef struct BMEdgeHit {
-       KnifeEdge *kfe;
+typedef struct KnifeLineHit {
        float hit[3], cagehit[3];
-       float realhit[3]; /* used in midpoint mode */
        float schit[2];
        float l; /* lambda along cut line */
        float perc; /* lambda along hit line */
-       KnifeVert *v; /* set if snapped to a vert */
+       float m; /* depth front-to-back */
+
+       /* Exactly one of kfe, v, or f should be non-NULL,
+        * saying whether cut line crosses and edge,
+        * is snapped to a vert, or is in the middle of some face. */
+       KnifeEdge *kfe;
+       KnifeVert *v;
        BMFace *f;
-} BMEdgeHit;
+} KnifeLineHit;
 
 typedef struct KnifePosData {
        float co[3];
@@ -152,8 +159,8 @@ typedef struct KnifeTool_OpData {
 
        GHash *origvertmap;
        GHash *origedgemap;
-
        GHash *kedgefacemap;
+       GHash *facetrimap;
 
        BMBVHTree *bmbvh;
 
@@ -164,7 +171,7 @@ typedef struct KnifeTool_OpData {
        float ethresh;
 
        /* used for drag-cutting */
-       BMEdgeHit *linehits;
+       KnifeLineHit *linehits;
        int totlinehit;
 
        /* Data for mouse-position-derived data (cur) and previous click (prev) */
@@ -198,10 +205,13 @@ typedef struct KnifeTool_OpData {
        } mode;
 
        int prevmode;
-       bool snap_midpoints, extend;
+       bool snap_midpoints;
        bool ignore_edge_snapping;
        bool ignore_vert_snapping;
 
+       /* use to check if we're currently dragging an angle snapped line */
+       bool is_angle_snapping;
+
        enum {
                ANGLE_FREE,
                ANGLE_0,
@@ -215,36 +225,27 @@ typedef struct KnifeTool_OpData {
 
 static ListBase *knife_get_face_kedges(KnifeTool_OpData *kcd, BMFace *f);
 
-#if 0
-static void knife_input_ray_cast(KnifeTool_OpData *kcd, const float mval[2],
-                                 float r_origin[3], float r_ray[3]);
-#endif
 static void knife_input_ray_segment(KnifeTool_OpData *kcd, const float mval[2], const float ofs,
                                     float r_origin[3], float r_dest[3]);
 
+static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f);
+
 static void knife_update_header(bContext *C, KnifeTool_OpData *kcd)
 {
-       #define HEADER_LENGTH 256
+#define HEADER_LENGTH 256
        char header[HEADER_LENGTH];
 
        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)"),
-                    kcd->snap_midpoints ? IFACE_("On") : IFACE_("Off"),
-                    kcd->ignore_edge_snapping ?  IFACE_("On") : IFACE_("Off"),
-                    kcd->angle_snapping ? IFACE_("On") : IFACE_("Off"),
-                    kcd->cut_through ? IFACE_("On") : IFACE_("Off"));
-
+                    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
 }
 
-#if 0
-BLI_INLINE int round_ftoi(float x)
-{
-       return x > 0.0f ?  (int)(x + 0.5f) : (int)(x - 0.5f);
-}
-#endif
-
 static void knife_project_v2(const KnifeTool_OpData *kcd, const float co[3], float sco[2])
 {
        ED_view3d_project_float_v2_m4(kcd->ar, co, sco, (float (*)[4])kcd->projmat);
@@ -265,7 +266,7 @@ static ListBase *knife_empty_list(KnifeTool_OpData *kcd)
        ListBase *lst;
 
        lst = BLI_memarena_alloc(kcd->arena, sizeof(ListBase));
-       lst->first = lst->last = NULL;
+       BLI_listbase_clear(lst);
        return lst;
 }
 
@@ -290,6 +291,12 @@ static Ref *find_ref(ListBase *lb, void *ref)
        return NULL;
 }
 
+static void knife_append_list_no_dup(KnifeTool_OpData *kcd, ListBase *lst, void *elem)
+{
+       if (!find_ref(lst, elem))
+               knife_append_list(kcd, lst, elem);
+}
+
 static KnifeEdge *new_knife_edge(KnifeTool_OpData *kcd)
 {
        kcd->totkedge++;
@@ -346,12 +353,17 @@ static KnifeVert *new_knife_vert(KnifeTool_OpData *kcd, const float co[3], const
 static KnifeVert *get_bm_knife_vert(KnifeTool_OpData *kcd, BMVert *v)
 {
        KnifeVert *kfv = BLI_ghash_lookup(kcd->origvertmap, v);
+       const float *cageco;
 
        if (!kfv) {
                BMIter bmiter;
                BMFace *f;
 
-               kfv = new_knife_vert(kcd, v->co, kcd->cagecos[BM_elem_index_get(v)]);
+               if (BM_elem_index_get(v) >= 0)
+                       cageco = kcd->cagecos[BM_elem_index_get(v)];
+               else
+                       cageco = v->co;
+               kfv = new_knife_vert(kcd, v->co, cageco);
                kfv->v = v;
                BLI_ghash_insert(kcd->origvertmap, v, kfv);
                BM_ITER_ELEM (f, &bmiter, v, BM_FACES_OF_VERT) {
@@ -387,6 +399,45 @@ static KnifeEdge *get_bm_knife_edge(KnifeTool_OpData *kcd, BMEdge *e)
        return kfe;
 }
 
+/* Record the index in kcd->em->looptris of first looptri triple for a given face,
+ * given an index for some triple in that array.
+ * This assumes that all of the triangles for a given face are contiguous
+ * in that array (as they are by the current tesselation routines).
+ * Actually store index + 1 in the hash, because 0 looks like "no entry"
+ * to hash lookup routine; will reverse this in the get routine.
+ * Doing this lazily rather than all at once for all faces.
+ */
+static void set_lowest_face_tri(KnifeTool_OpData *kcd, BMFace *f, int index)
+{
+       int i;
+
+       if (BLI_ghash_lookup(kcd->facetrimap, f))
+               return;
+
+       BLI_assert(index >= 0 && index < kcd->em->tottri);
+       BLI_assert(kcd->em->looptris[index][0]->f == f);
+       for (i = index - 1; i >= 0; i--) {
+               if (kcd->em->looptris[i][0]->f != f) {
+                       i++;
+                       break;
+               }
+       }
+       if (i == -1)
+               i++;
+
+       BLI_ghash_insert(kcd->facetrimap, f, SET_INT_IN_POINTER(i + 1));
+}
+
+/* This should only be called for faces that have had a lowest face tri set by previous function */
+static int get_lowest_face_tri(KnifeTool_OpData *kcd, BMFace *f)
+{
+       int ans;
+
+       ans = GET_INT_FROM_POINTER(BLI_ghash_lookup(kcd->facetrimap, f));
+       BLI_assert(ans != 0);
+       return ans - 1;
+}
+
 /* User has just clicked for first time or first time after a restart (E key).
  * Copy the current position data into prev. */
 static void knife_start_cut(KnifeTool_OpData *kcd)
@@ -400,7 +451,7 @@ static void knife_start_cut(KnifeTool_OpData *kcd)
                BMVert *v0;
 
                knife_input_ray_segment(kcd, kcd->curr.mval, 1.0f, origin, origin_ofs);
-               v0 = BM_vert_at_index(kcd->em->bm, 0);
+               v0 = BM_vert_at_index_find(kcd->em->bm, 0);
                if (v0) {
                        closest_to_line_v3(kcd->prev.cage, v0->co, origin_ofs, origin);
                        copy_v3_v3(kcd->prev.co, kcd->prev.cage); /*TODO: do we need this? */
@@ -430,12 +481,6 @@ static ListBase *knife_get_face_kedges(KnifeTool_OpData *kcd, BMFace *f)
        return lst;
 }
 
-/* finds the proper face to restrict face fill to */
-static void knife_find_basef(KnifeEdge *kfe)
-{
-       kfe->basef = knife_find_common_face(&kfe->v1->faces, &kfe->v2->faces);
-}
-
 static void knife_edge_append_face(KnifeTool_OpData *kcd, KnifeEdge *kfe, BMFace *f)
 {
        knife_append_list(kcd, knife_get_face_kedges(kcd, f), kfe);
@@ -493,410 +538,272 @@ static KnifeVert *knife_split_edge(KnifeTool_OpData *kcd, KnifeEdge *kfe, float
        return newkfe->v2;
 }
 
-/* Make a single KnifeEdge for cut from kcd->prev to kcd->curr.
- * and move cur data to prev. */
-static void knife_add_single_cut(KnifeTool_OpData *kcd)
+/* primary key: lambda along cut
+ * secondary key: lambda along depth
+ * tertiary key: pointer comparisons of verts if both snapped to verts
+ */
+static int linehit_compare(const void *vlh1, const void *vlh2)
 {
-       KnifeEdge *kfe = new_knife_edge(kcd), *kfe2 = NULL, *kfe3 = NULL;
-
-       if (kcd->prev.vert && kcd->prev.vert == kcd->curr.vert)
-               return;
-       if (kcd->prev.edge && kcd->prev.edge == kcd->curr.edge)
-               return;
-
-       kfe->draw = true;
+       const KnifeLineHit *lh1 = vlh1;
+       const KnifeLineHit *lh2 = vlh2;
 
-       if (kcd->prev.vert) {
-               kfe->v1 = kcd->prev.vert;
-       }
-       else if (kcd->prev.edge) {
-               kfe->v1 = knife_split_edge(kcd, kcd->prev.edge, kcd->prev.co, &kfe2);
-       }
+       if      (lh1->l < lh2->l) return -1;
+       else if (lh1->l > lh2->l) return  1;
        else {
-               kfe->v1 = new_knife_vert(kcd, kcd->prev.co, kcd->prev.co);
-               kfe->v1->draw = kfe->draw = !kcd->prev.is_space;
-               kfe->v1->in_space = kcd->prev.is_space;
-               kfe->draw = !kcd->prev.is_space;
-               kfe->v1->is_face = true;
-               if (kfe->v1->draw && kcd->prev.bmface)
-                       knife_append_list(kcd, &kfe->v1->faces, kcd->prev.bmface);
+               if      (lh1->m < lh2->m) return -1;
+               else if (lh1->m > lh2->m) return  1;
+               else {
+                       if      (lh1->v < lh2->v) return -1;
+                       else if (lh1->v > lh2->v) return  1;
+                       else return 0;
+               }
        }
+}
 
-       if (kcd->curr.vert) {
-               kfe->v2 = kcd->curr.vert;
-       }
-       else if (kcd->curr.edge) {
-               kfe->v2 = knife_split_edge(kcd, kcd->curr.edge, kcd->curr.co, &kfe3);
-               kcd->curr.vert = kfe->v2;
-       }
-       else {
-               kfe->v2 = new_knife_vert(kcd, kcd->curr.co, kcd->curr.co);
-               kfe->v2->draw = !kcd->curr.is_space;
-               kfe->v2->is_face = true;
-               kfe->v2->in_space = kcd->curr.is_space;
-               if (kfe->v2->draw && kcd->curr.bmface)
-                       knife_append_list(kcd, &kfe->v2->faces, kcd->curr.bmface);
+/*
+ * Sort linehits by distance along cut line, and secondarily from
+ * front to back (from eye), and tertiarily by snap vertex,
+ * and remove any duplicates.
+ */
+static void prepare_linehits_for_cut(KnifeTool_OpData *kcd)
+{
+       KnifeLineHit *linehits, *lhi, *lhj;
+       int i, j, n;
 
-               if (kcd->curr.is_space)
-                       kfe->draw = false;
+       n = kcd->totlinehit;
+       linehits = kcd->linehits;
+       if (n == 0)
+               return;
 
-               kcd->curr.vert = kfe->v2;
+       qsort(linehits, n, sizeof(KnifeLineHit), linehit_compare);
+
+       /* Remove any edge hits that are preceded or followed
+        * by a vertex hit that is very near. Mark such edge hits using
+        * l == -1 and then do another pass to actually remove.
+        * Also remove all but one of a series of vertex hits for the same vertex. */
+       for (i = 0; i < n; i++) {
+               lhi = &linehits[i];
+               if (lhi->v) {
+                       for (j = i - 1; j >= 0; j--) {
+                               lhj = &linehits[j];
+                               if (!lhj->kfe ||
+                                   fabsf(lhi->l - lhj->l) > KNIFE_FLT_EPSBIG ||
+                                   fabsf(lhi->m - lhj->m) > KNIFE_FLT_EPSBIG)
+                               {
+                                       break;
+                               }
+                               lhj->l = -1.0f;
+                       }
+                       for (j = i + 1; j < n; j++) {
+                               lhj = &linehits[j];
+                               if (fabsf(lhi->l - lhj->l) > KNIFE_FLT_EPSBIG ||
+                                   fabsf(lhi->m - lhj->m) > KNIFE_FLT_EPSBIG)
+                               {
+                                       break;
+                               }
+                               if (lhj->kfe || lhi->v == lhj->v) {
+                                       lhj->l = -1.0f;
+                               }
+                       }
+               }
        }
 
-       knife_find_basef(kfe);
-
-       knife_add_to_vert_edges(kcd, kfe);
-
-       if (kfe->basef && !find_ref(&kfe->faces, kfe->basef))
-               knife_edge_append_face(kcd, kfe, kfe->basef);
-
-       /* sanity check to make sure we're in the right edge/face lists */
-       if (kcd->curr.bmface) {
-               if (!find_ref(&kfe->faces, kcd->curr.bmface)) {
-                       knife_edge_append_face(kcd, kfe, kcd->curr.bmface);
+       /* 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 */
                }
-
-               if (kcd->prev.bmface && kcd->prev.bmface != kcd->curr.bmface) {
-                       if (!find_ref(&kfe->faces, kcd->prev.bmface)) {
-                               knife_edge_append_face(kcd, kfe, kcd->prev.bmface);
+               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));
+                       }
+                       else {
+                               if (i + 1 != j)
+                                       memcpy(&linehits[i + 1], &linehits[j], sizeof(KnifeLineHit));
+                               i++;
                        }
+                       j++;
                }
        }
-
-       /* set up for next cut */
-       kcd->prev = kcd->curr;
+       kcd->totlinehit = i + 1;
 }
 
-static int verge_linehit(const void *vlh1, const void *vlh2)
+/* Add hit to list of hits in facehits[f], where facehits is a map, if not already there */
+static void add_hit_to_facehits(KnifeTool_OpData *kcd, GHash *facehits, BMFace *f, KnifeLineHit *hit)
 {
-       const BMEdgeHit *lh1 = vlh1, *lh2 = vlh2;
+       ListBase *lst = BLI_ghash_lookup(facehits, f);
 
-       if      (lh1->l < lh2->l) return -1;
-       else if (lh1->l > lh2->l) return 1;
-       else return 0;
+       if (!lst) {
+               lst = knife_empty_list(kcd);
+               BLI_ghash_insert(facehits, f, lst);
+       }
+       knife_append_list_no_dup(kcd, lst, hit);
 }
 
-/* If there's a linehit connected (same face) as testi in range [firsti, lasti], return the first such, else -1.
- * If testi is out of range, look for connection to f instead, if f is non-NULL */
-static int find_connected_linehit(KnifeTool_OpData *kcd, int testi, BMFace *f, int firsti, int lasti)
+static void knife_add_single_cut(KnifeTool_OpData *kcd, KnifeLineHit *lh1, KnifeLineHit *lh2, BMFace *f)
 {
-       int i;
+       KnifeEdge *kfe, *kfe2;
 
-       for (i = firsti; i <= lasti; i++) {
-               if (testi >= 0 && testi < kcd->totlinehit) {
-                       if (knife_find_common_face(&kcd->linehits[testi].kfe->faces,
-                                                  &kcd->linehits[i].kfe->faces))
-                       {
-                               return i;
-                       }
-               }
-               else if (f) {
-                       if (find_ref(&kcd->linehits[i].kfe->faces, f))
-                               return i;
-               }
+       if ((lh1->v && lh1->v == lh2->v) ||
+           (lh1->kfe && lh1->kfe == lh2->kfe))
+       {
+               return;
        }
-       return -1;
-}
 
-/* Sort in order of distance along cut line, but take care when distances are equal */
-static void knife_sort_linehits(KnifeTool_OpData *kcd)
-{
-       int i, j, k, nexti, nsame;
+       /* Check if edge actually lies within face (might not, if this face is concave) */
+       if ((lh1->v && !lh1->kfe) && (lh2->v && !lh2->kfe)) {
+               if (!knife_verts_edge_in_face(lh1->v, lh2->v, f)) {
+                       return;
+               }
+       }
 
-       qsort(kcd->linehits, kcd->totlinehit, sizeof(BMEdgeHit), verge_linehit);
+       kfe = new_knife_edge(kcd);
+       kfe->draw = true;
+       kfe->basef = f;
 
-       /* for ranges of equal "l", swap if neccesary to make predecessor and
-        * successor faces connected to the linehits at either end of the range */
-       for (i = 0; i < kcd->totlinehit - 1; i = nexti) {
-               for (j = i + 1; j < kcd->totlinehit; j++) {
-                       if (fabsf(kcd->linehits[j].l - kcd->linehits[i].l) > KNIFE_FLT_EPS)
-                               break;
-               }
-               nexti = j;
-               j--;
-               nsame = j - i;
-               if (nsame > 0) {
-                       /* find something connected to predecessor of equal range */
-                       k = find_connected_linehit(kcd, i - 1, kcd->prev.bmface, i, j);
-                       if (k != -1) {
-                               if (k != i) {
-                                       SWAP(BMEdgeHit, kcd->linehits[i], kcd->linehits[k]);
-                               }
-                               i++;
-                               nsame--;
-                       }
-                       if (nsame > 0) {
-                               /* find something connected to successor of equal range */
-                               k = find_connected_linehit(kcd, j + 1, kcd->curr.bmface, i, j);
-                               if (k != -1 && k != j) {
-                                       SWAP(BMEdgeHit, kcd->linehits[j], kcd->linehits[k]);
-                               }
-                       }
-                       /* rest of same range doesn't matter because we won't connect them */
-               }
+       if (lh1->v) {
+               kfe->v1 = lh1->v;
+       }
+       else if (lh1->kfe) {
+               kfe->v1 = knife_split_edge(kcd, lh1->kfe, lh1->cagehit, &kfe2);
+               lh1->v = kfe->v1;  /* record the KnifeVert for this hit  */
+       }
+       else {
+               BLI_assert(lh1->f);
+               kfe->v1 = new_knife_vert(kcd, lh1->hit, lh1->cagehit);
+               kfe->v1->draw = 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 */
        }
-}
 
-static void knife_add_single_cut_through(KnifeTool_OpData *kcd, KnifeVert *v1, KnifeVert *v2, BMFace *f)
-{
-       KnifeEdge *kfenew;
+       if (lh2->v) {
+               kfe->v2 = lh2->v;
+       }
+       else if (lh2->kfe) {
+               kfe->v2 = knife_split_edge(kcd, lh2->kfe, lh2->cagehit, &kfe2);
+               lh2->v = kfe->v2;  /* future uses of lh2 won't split again */
+       }
+       else {
+               BLI_assert(lh2->f);
+               kfe->v2 = new_knife_vert(kcd, lh2->hit, lh2->cagehit);
+               kfe->v2->draw = 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 */
+       }
 
-       kfenew = new_knife_edge(kcd);
-       kfenew->basef = f;
-       kfenew->v1 = v1;
-       kfenew->v2 = v2;
-       kfenew->draw = true;
+       knife_add_to_vert_edges(kcd, kfe);
 
-       knife_add_to_vert_edges(kcd, kfenew);
+       /* TODO: check if this is ever needed */
+       if (kfe->basef && !find_ref(&kfe->faces, kfe->basef))
+               knife_edge_append_face(kcd, kfe, kfe->basef);
 
-       if (!find_ref(&kfenew->faces, f))
-               knife_edge_append_face(kcd, kfenew, f);
 }
 
-static void knife_get_vert_faces(KnifeTool_OpData *kcd, KnifeVert *kfv, BMFace *facef, ListBase *lst)
+/* Given a list of KnifeLineHits for one face, sorted by l
+ * and then by m, make the required KnifeVerts and
+ * KnifeEdges.
+ */
+static void knife_cut_face(KnifeTool_OpData *kcd, BMFace *f, ListBase *hits)
 {
-       BMIter bmiter;
-       BMFace *f;
        Ref *r;
+       KnifeLineHit *lh, *prevlh;
+       int n;
 
-       if (kfv->is_face && facef) {
-               knife_append_list(kcd, lst, facef);
-       }
-       else if (kfv->v) {
-               BM_ITER_ELEM (f, &bmiter, kfv->v, BM_FACES_OF_VERT) {
-                       knife_append_list(kcd, lst, f);
-               }
-       }
-       else {
-               for (r = kfv->faces.first; r; r = r->next) {
-                       knife_append_list(kcd, lst, r->ref);
-               }
-       }
-}
+       (void) kcd;
 
-static void knife_get_edge_faces(KnifeTool_OpData *kcd, KnifeEdge *kfe, ListBase *lst)
-{
-       BMIter bmiter;
-       BMFace *f;
+       n = BLI_countlist(hits);
+       if (n < 2)
+               return;
 
-       if (kfe->e) {
-               BM_ITER_ELEM (f, &bmiter, kfe->e, BM_FACES_OF_EDGE) {
-                       knife_append_list(kcd, lst, f);
-               }
+       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;
        }
+
 }
 
-/* BMESH_TODO: add more functionality to cut-through:
- *    - cutting "in face" (e.g., holes) should cut in all faces, not just visible one
- *    - perhaps improve O(n^2) algorithm used here */
-static void knife_cut_through(KnifeTool_OpData *kcd)
+/* User has just left-clicked after the first time.
+ * Add all knife cuts implied by line from prev to curr.
+ * If that line crossed edges then kcd->linehits will be non-NULL.
+ * Make all of the KnifeVerts and KnifeEdges implied by this cut.
+ */
+static void knife_add_cut(KnifeTool_OpData *kcd)
 {
-       BMEdgeHit *lh, *lh2;
+       int i;
+       KnifeLineHit *lh;
+       GHash *facehits;
        BMFace *f;
-       KnifeEdge *kfe, *kfe2, *kfe3;
-       KnifeVert *v1, *v2, *firstv = NULL, *lastv = NULL;
-       ListBase firstfaces = {NULL, NULL}, lastfaces = {NULL, NULL};
-       Ref *r, *r2;
-       KnifeEdge **splitkfe;
-       int i, j;
-
-       if (!kcd->totlinehit) {
-               /* if no linehits then no interesting back face stuff to do */
-               knife_add_single_cut(kcd);
+       Ref *r;
+       GHashIterator giter;
+       ListBase *lst;
+
+       prepare_linehits_for_cut(kcd);
+       if (kcd->totlinehit == 0) {
+               kcd->prev = kcd->curr;
                return;
        }
 
-       /* TODO: probably don't need to sort at all */
-       qsort(kcd->linehits, kcd->totlinehit, sizeof(BMEdgeHit), verge_linehit);
-       splitkfe = MEM_callocN(kcd->totlinehit * sizeof(KnifeEdge *), "knife_cut_through");
-
-       if (kcd->prev.vert) {
-               if (kcd->prev.vert == kcd->curr.vert)
-                       return;
-               firstv = kcd->prev.vert;
-               knife_get_vert_faces(kcd, firstv, kcd->prev.bmface, &firstfaces);
-       }
-       else if (kcd->prev.edge) {
-               if (kcd->prev.edge == kcd->curr.edge)
-                       return;
-               firstv = knife_split_edge(kcd, kcd->prev.edge, kcd->prev.co, &kfe3);
-               knife_get_edge_faces(kcd, kcd->prev.edge, &firstfaces);
-       }
-
-       if (kcd->curr.vert) {
-               lastv = kcd->curr.vert;
-               knife_get_vert_faces(kcd, lastv, kcd->curr.bmface, &lastfaces);
-       }
-       else if (kcd->curr.edge) {
-               lastv = knife_split_edge(kcd, kcd->curr.edge, kcd->curr.co, &kfe3);
-               knife_get_edge_faces(kcd, kcd->curr.edge, &lastfaces);
-       }
-
-       if (firstv) {
-               /* For each face incident to firstv,
-                * find the first following linehit (if any) sharing that face and connect */
-               for (r = firstfaces.first; r; r = r->next) {
-                       bool found = false;
-                       f = r->ref;
-                       for (j = 0, lh2 = kcd->linehits; j < kcd->totlinehit && !found; j++, lh2++) {
-                               kfe2 = lh2->kfe;
-                               for (r2 = kfe2->faces.first; r2; r2 = r2->next) {
-                                       if (r2->ref == f) {
-                                               v2 = splitkfe[j] ? kfe2->v1 : knife_split_edge(kcd, kfe2, lh2->hit, &splitkfe[j]);
-                                               knife_add_single_cut_through(kcd, firstv, v2, f);
-                                               found = true;
-                                               break;
-                                       }
-                               }
+       /* 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];
+               if (lh->f) {
+                       add_hit_to_facehits(kcd, facehits, lh->f, lh);
+               }
+               if (lh->v) {
+                       for (r = lh->v->faces.first; r; r = r->next) {
+                               add_hit_to_facehits(kcd, facehits, r->ref, lh);
                        }
-                       if (!found && lastv) {
-                               for (r2 = lastfaces.first; r2; r2 = r2->next) {
-                                       if (r2->ref == f) {
-                                               knife_add_single_cut_through(kcd, firstv, lastv, f);
-                                               break;
-                                       }
-                               }
+               }
+               if (lh->kfe) {
+                       for (r = lh->kfe->faces.first; r; r = r->next) {
+                               add_hit_to_facehits(kcd, facehits, r->ref, lh);
                        }
                }
        }
 
-       for (i = 0, lh = kcd->linehits; i < kcd->totlinehit; i++, lh++) {
-               kfe = lh->kfe;
-
-               /* For each face attached to edge for this linehit,
-                * find the first following linehit (if any) sharing that face and connect */
-               for (r = kfe->faces.first; r; r = r->next) {
-                       bool found = false;
-                       f = r->ref;
-                       for (j = i + 1, lh2 = lh + 1; j < kcd->totlinehit && !found; j++, lh2++) {
-                               kfe2 = lh2->kfe;
-                               for (r2 = kfe2->faces.first; r2; r2 = r2->next) {
-                                       if (r2->ref == f) {
-                                               v1 = splitkfe[i] ? kfe->v1 : knife_split_edge(kcd, kfe, lh->hit, &splitkfe[i]);
-                                               v2 = splitkfe[j] ? kfe2->v1 : knife_split_edge(kcd, kfe2, lh2->hit, &splitkfe[j]);
-                                               knife_add_single_cut_through(kcd, v1, v2, f);
-                                               found = true;
-                                               break;
-                                       }
-                               }
-                       }
-                       if (!found && lastv) {
-                               for (r2 = lastfaces.first; r2; r2 = r2->next) {
-                                       if (r2->ref == f) {
-                                               v1 = splitkfe[i] ? kfe->v1 : knife_split_edge(kcd, kfe, lh->hit, &splitkfe[i]);
-                                               knife_add_single_cut_through(kcd, v1, lastv, f);
-                                               break;
-                                       }
-                               }
-                       }
-               }
+       /* Note: as following loop progresses, the 'v' fields of
+        * the linehits will be filled in (as edges are split or
+        * in-face verts are made), so it may be true that both
+        * the v and the kfe or f fields will be non-NULL. */
+       GHASH_ITER (giter, facehits) {
+               f = (BMFace *)BLI_ghashIterator_getKey(&giter);
+               lst = (ListBase *)BLI_ghashIterator_getValue(&giter);
+               knife_cut_face(kcd, f, lst);
+       }
+
+       /* 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 */
+               kcd->prev.bmface = NULL;
+               kcd->prev.vert = kcd->linehits[kcd->totlinehit - 1].v;
        }
 
-       MEM_freeN(splitkfe);
+       BLI_ghash_free(facehits, NULL, NULL);
        MEM_freeN(kcd->linehits);
        kcd->linehits = NULL;
        kcd->totlinehit = 0;
-
-       /* set up for next cut */
-       kcd->curr.vert = lastv;
-       kcd->prev = kcd->curr;
 }
 
-/* User has just left-clicked after the first time.
- * Add all knife cuts implied by line from prev to curr.
- * If that line crossed edges then kcd->linehits will be non-NULL. */
-static void knife_add_cut(KnifeTool_OpData *kcd)
+static void knife_finish_cut(KnifeTool_OpData *kcd)
 {
-       KnifePosData savcur = kcd->curr;
-
-       if (kcd->cut_through) {
-               knife_cut_through(kcd);
-       }
-       else if (kcd->linehits) {
-               BMEdgeHit *lh, *lastlh, *firstlh;
-               int i;
-
-               knife_sort_linehits(kcd);
-
-               lh = kcd->linehits;
-               lastlh = firstlh = NULL;
-               for (i = 0; i < kcd->totlinehit; i++, (lastlh = lh), lh++) {
-                       BMFace *f = lastlh ? lastlh->f : lh->f;
-
-                       if (lastlh && len_squared_v3v3(lastlh->hit, lh->hit) == 0.0f) {
-                               if (!firstlh)
-                                       firstlh = lastlh;
-                               continue;
-                       }
-                       else if (lastlh && firstlh) {
-                               if (firstlh->v || lastlh->v) {
-                                       KnifeVert *kfv = firstlh->v ? firstlh->v : lastlh->v;
-
-                                       kcd->prev.vert = kfv;
-                                       copy_v3_v3(kcd->prev.co, firstlh->hit);
-                                       copy_v3_v3(kcd->prev.cage, firstlh->cagehit);
-                                       kcd->prev.edge = NULL;
-                                       kcd->prev.bmface = f;
-                                       /* TODO: should we set prev.in_space = 0 ? */
-                               }
-                               lastlh = firstlh = NULL;
-                       }
-
-                       if (len_squared_v3v3(kcd->prev.cage, lh->realhit) < KNIFE_FLT_EPS_SQUARED)
-                               continue;
-                       if (len_squared_v3v3(kcd->curr.cage, lh->realhit) < KNIFE_FLT_EPS_SQUARED)
-                               continue;
-
-                       /* first linehit may be down face parallel to view */
-                       if (!lastlh && fabsf(lh->l) < KNIFE_FLT_EPS)
-                               continue;
-
-                       if (kcd->prev.is_space) {
-                               kcd->prev.is_space = 0;
-                               copy_v3_v3(kcd->prev.co, lh->hit);
-                               copy_v3_v3(kcd->prev.cage, lh->cagehit);
-                               kcd->prev.vert = NULL;
-                               kcd->prev.edge = lh->kfe;
-                               kcd->prev.bmface = lh->f;
-                               continue;
-                       }
-
-                       kcd->curr.is_space = 0;
-                       kcd->curr.edge = lh->kfe;
-                       kcd->curr.bmface = lh->f;
-                       kcd->curr.vert = lh->v;
-                       copy_v3_v3(kcd->curr.co, lh->hit);
-                       copy_v3_v3(kcd->curr.cage, lh->cagehit);
-
-                       /* don't draw edges down faces parallel to view */
-                       if (lastlh && fabsf(lastlh->l - lh->l) < KNIFE_FLT_EPS) {
-                               kcd->prev = kcd->curr;
-                               continue;
-                       }
-
-                       knife_add_single_cut(kcd);
-               }
-
-               if (savcur.is_space) {
-                       kcd->prev = savcur;
-               }
-               else {
-                       kcd->curr = savcur;
-                       knife_add_single_cut(kcd);
-               }
-
+       if (kcd->linehits) {
                MEM_freeN(kcd->linehits);
                kcd->linehits = NULL;
                kcd->totlinehit = 0;
        }
-       else {
-               knife_add_single_cut(kcd);
-       }
-}
-
-static void knife_finish_cut(KnifeTool_OpData *UNUSED(kcd))
-{
-
 }
 
 static void knifetool_draw_angle_snapping(const KnifeTool_OpData *kcd)
@@ -1040,6 +947,24 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
                glLineWidth(1.0);
        }
 
+       if (kcd->prev.vert) {
+               glColor3ubv(kcd->colors.point);
+               glPointSize(11);
+
+               glBegin(GL_POINTS);
+               glVertex3fv(kcd->prev.cage);
+               glEnd();
+       }
+
+       if (kcd->prev.bmface) {
+               glColor3ubv(kcd->colors.curpoint);
+               glPointSize(9);
+
+               glBegin(GL_POINTS);
+               glVertex3fv(kcd->prev.cage);
+               glEnd();
+       }
+
        if (kcd->curr.edge) {
                glColor3ubv(kcd->colors.edge);
                glLineWidth(2.0);
@@ -1070,10 +995,7 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
        }
 
        if (kcd->totlinehit > 0) {
-               const float vthresh4 = kcd->vthresh / 4.0f;
-               const float vthresh4_sq = vthresh4 * vthresh4;
-
-               BMEdgeHit *lh;
+               KnifeLineHit *lh;
                int i;
 
                glEnable(GL_BLEND);
@@ -1085,22 +1007,8 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
                glBegin(GL_POINTS);
                lh = kcd->linehits;
                for (i = 0; i < kcd->totlinehit; i++, lh++) {
-                       float sv1[2], sv2[2];
-
-                       knife_project_v2(kcd, lh->kfe->v1->cageco, sv1);
-                       knife_project_v2(kcd, lh->kfe->v2->cageco, sv2);
-                       knife_project_v2(kcd, lh->cagehit, lh->schit);
-
-                       if (len_squared_v2v2(lh->schit, sv1) < vthresh4_sq) {
-                               copy_v3_v3(lh->cagehit, lh->kfe->v1->cageco);
-                               glVertex3fv(lh->cagehit);
-                               lh->v = lh->kfe->v1;
-                       }
-                       else if (len_squared_v2v2(lh->schit, sv2) < vthresh4_sq) {
-                               copy_v3_v3(lh->cagehit, lh->kfe->v2->cageco);
+                       if (lh->v)
                                glVertex3fv(lh->cagehit);
-                               lh->v = lh->kfe->v2;
-                       }
                }
                glEnd();
 
@@ -1110,7 +1018,8 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
                glBegin(GL_POINTS);
                lh = kcd->linehits;
                for (i = 0; i < kcd->totlinehit; i++, lh++) {
-                       glVertex3fv(lh->cagehit);
+                       if (!lh->v)
+                               glVertex3fv(lh->cagehit);
                }
                glEnd();
                glDisable(GL_BLEND);
@@ -1163,179 +1072,72 @@ static void knifetool_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
        if (v3d->zbuf) glEnable(GL_DEPTH_TEST);
 }
 
-static float len_v3_tri_side_max(const float v1[3], const float v2[3], const float v3[3])
-{
-       const float s1 = len_squared_v3v3(v1, v2);
-       const float s2 = len_squared_v3v3(v2, v3);
-       const float s3 = len_squared_v3v3(v3, v1);
-
-       return sqrtf(max_fff(s1, s2, s3));
-}
-
-static BMEdgeHit *knife_edge_tri_isect(KnifeTool_OpData *kcd, BMBVHTree *bmtree,
-                                       const float v1[3],  const float v2[3], const float v3[3],
-                                       SmallHash *ehash, bglMats *mats, int *count)
-{
-       BVHTree *tree2 = BLI_bvhtree_new(3, FLT_EPSILON * 4, 8, 8), *tree = BKE_bmbvh_tree_get(bmtree);
-       BMEdgeHit *edges = NULL;
-       BLI_array_declare(edges);
-       BVHTreeOverlap *results, *result;
-       BMLoop **ls;
-       float cos[9], lambda;
-       unsigned int tot = 0;
-       int i;
-
-       /* for comparing distances, error of intersection depends on triangle scale.
-        * need to scale down before squaring for accurate comparison */
-       const float depsilon = (FLT_EPSILON / 2.0f) * len_v3_tri_side_max(v1, v2, v3);
-       const float depsilon_sq = depsilon * depsilon;
-
-       copy_v3_v3(cos + 0, v1);
-       copy_v3_v3(cos + 3, v2);
-       copy_v3_v3(cos + 6, v3);
-
-       BLI_bvhtree_insert(tree2, 0, cos, 3);
-       BLI_bvhtree_balance(tree2);
-
-       result = results = BLI_bvhtree_overlap(tree, tree2, &tot);
-
-       for (i = 0; i < tot; i++, result++) {
-               BMLoop *l1;
-               BMFace *f_hit;
-               ListBase *lst;
-               Ref *ref;
-
-               ls = (BMLoop **)kcd->em->looptris[result->indexA];
-
-               l1 = ls[0];
-               lst = knife_get_face_kedges(kcd, l1->f);
+/* Find intersection of v1-v2 with face f.
+ * Only take intersections that are at least face_tol (in screen space) away
+ * from other intersection elements.
+ * If v1-v2 is coplanar with f, call that "no intersection though
+ * it really means "infinite number of intersections".
+ * In such a case we should have gotten hits on edges or verts of the face. */
+static bool knife_ray_intersect_face(KnifeTool_OpData *kcd,
+                                     const float s[2],
+                                     const float v1[3], const float v2[3],
+                                     BMFace *f,
+                                     const float face_tol,
+                                     float intersectp[3])
+{
+       int tottri, tri_i;
+       float lv1[3], lv2[3], lv3[3], raydir[3];
+       float tri_norm[3], tri_plane[4];
+       float se1[2], se2[2];
+       float d, lambda;
+       BMLoop **tri;
+       ListBase *lst;
+       Ref *ref;
+       KnifeEdge *kfe;
 
-               for (ref = lst->first; ref; ref = ref->next) {
-                       KnifeEdge *kfe = ref->ref;
+       sub_v3_v3v3(raydir, v2, v1);
+       normalize_v3(raydir);
+       tri_i = get_lowest_face_tri(kcd, f);
+       tottri = kcd->em->tottri;
+       BLI_assert(tri_i >= 0 && tri_i < tottri);
 
-                       if (BLI_smallhash_haskey(ehash, (uintptr_t)kfe)) {
-                               continue;  /* We already found a hit on this knife edge */
+       for (; tri_i < tottri; tri_i++) {
+               tri = kcd->em->looptris[tri_i];
+               if (tri[0]->f != f)
+                       break;
+               copy_v3_v3(lv1, kcd->cagecos[BM_elem_index_get(tri[0]->v)]);
+               copy_v3_v3(lv2, kcd->cagecos[BM_elem_index_get(tri[1]->v)]);
+               copy_v3_v3(lv3, kcd->cagecos[BM_elem_index_get(tri[2]->v)]);
+               /* using epsilon test in case ray is directly through an internal
+                * tesselation edge and might not hit either tesselation tri with
+                * an exact test;
+                * we will exclude hits near real edges by a later test */
+               if (isect_ray_tri_epsilon_v3(v1, raydir, lv1, lv2, lv3, &lambda, NULL, KNIFE_FLT_EPS)) {
+                       /* check if line coplanar with tri */
+                       normal_tri_v3(tri_norm, lv1, lv2, lv3);
+                       plane_from_point_normal_v3(tri_plane, lv1, tri_norm);
+                       if ((fabsf(dist_squared_to_plane_v3(v1, tri_plane)) < KNIFE_FLT_EPS) &&
+                           (fabsf(dist_squared_to_plane_v3(v2, tri_plane)) < KNIFE_FLT_EPS))
+                       {
+                               return false;
                        }
-
-                       if (isect_line_tri_v3(kfe->v1->cageco, kfe->v2->cageco, v1, v2, v3, &lambda, NULL)) {
-                               float p[3], no[3], view[3], sp[2];
-
-                               interp_v3_v3v3(p, kfe->v1->cageco, kfe->v2->cageco, lambda);
-
-                               if (kcd->curr.vert && len_squared_v3v3(kcd->curr.vert->cageco, p) < depsilon_sq) {
-                                       continue;
-                               }
-                               if (kcd->prev.vert && len_squared_v3v3(kcd->prev.vert->cageco, p) < depsilon_sq) {
-                                       continue;
-                               }
-                               if (len_squared_v3v3(kcd->prev.cage, p) < depsilon_sq ||
-                                   len_squared_v3v3(kcd->curr.cage, p) < depsilon_sq)
-                               {
-                                       continue;
-                               }
-                               if ((kcd->vc.rv3d->rflag & RV3D_CLIPPING) &&
-                                   ED_view3d_clipping_test(kcd->vc.rv3d, p, true))
-                               {
-                                       continue;
-                               }
-
-                               knife_project_v2(kcd, p, sp);
-                               ED_view3d_unproject(mats, view, sp[0], sp[1], 0.0f);
-                               mul_m4_v3(kcd->ob->imat, view);
-
-                               if (kcd->cut_through) {
-                                       f_hit = NULL;
-                               }
-                               else {
-                                       /* check if this point is visible in the viewport */
-                                       float p1[3], lambda1;
-
-                                       /* if face isn't planer, p may be behind the current tesselated tri,
-                                        * so move it onto that and then a little towards eye */
-                                       if (isect_line_tri_v3(p, view, ls[0]->v->co, ls[1]->v->co, ls[2]->v->co, &lambda1, NULL)) {
-                                               interp_v3_v3v3(p1, p, view, lambda1);
-                                       }
-                                       else {
-                                               copy_v3_v3(p1, p);
-                                       }
-                                       sub_v3_v3(view, p1);
-                                       normalize_v3(view);
-
-                                       copy_v3_v3(no, view);
-                                       mul_v3_fl(no, 0.003);
-
-                                       /* go towards view a bit */
-                                       add_v3_v3(p1, no);
-                                               
-                                       /* ray cast */
-                                       f_hit = BKE_bmbvh_ray_cast(bmtree, p1, no, NULL, NULL, NULL);
-                               }
-
-                               /* ok, if visible add the new point */
-                               if (!f_hit && !BLI_smallhash_haskey(ehash, (uintptr_t)kfe)) {
-                                       BMEdgeHit hit;
-       
-                                       if (len_squared_v3v3(p, kcd->curr.co) < depsilon_sq ||
-                                           len_squared_v3v3(p, kcd->prev.co) < depsilon_sq)
-                                       {
-                                               continue;
-                                       }
-
-                                       hit.kfe = kfe;
-                                       hit.v = NULL;
-
-                                       knife_find_basef(kfe);
-                                       hit.f = kfe->basef;
-                                       hit.perc = len_v3v3(p, kfe->v1->cageco) / len_v3v3(kfe->v1->cageco, kfe->v2->cageco);
-                                       copy_v3_v3(hit.cagehit, p);
-
-                                       interp_v3_v3v3(p, kfe->v1->co, kfe->v2->co, hit.perc);
-                                       copy_v3_v3(hit.realhit, p);
-
-                                       /* BMESH_TODO: should also snap to vertices */
-                                       if (kcd->snap_midpoints) {
-                                               float perc = hit.perc;
-
-                                               /* select the closest from the edge endpoints or the midpoint */
-                                               if (perc < 0.25f) {
-                                                       perc = 0.0f;
-                                               }
-                                               else if (perc < 0.75f) {
-                                                       perc = 0.5f;
-                                               }
-                                               else {
-                                                       perc = 1.0f;
-                                               }
-
-                                               interp_v3_v3v3(hit.hit, kfe->v1->co, kfe->v2->co, perc);
-                                               interp_v3_v3v3(hit.cagehit, kfe->v1->cageco, kfe->v2->cageco, perc);
-                                       }
-                                       else {
-                                               copy_v3_v3(hit.hit, p);
-                                       }
-                                       knife_project_v2(kcd, hit.cagehit, hit.schit);
-
-                                       BLI_array_append(edges, hit);
-                                       BLI_smallhash_insert(ehash, (uintptr_t)kfe, NULL);
+                       copy_v3_v3(intersectp, v1);
+                       madd_v3_v3fl(intersectp, raydir, lambda);
+                       /* Now check that far enough away from verts and edges */
+                       lst = knife_get_face_kedges(kcd, f);
+                       for (ref = lst->first; ref; ref = ref->next) {
+                               kfe = ref->ref;
+                               knife_project_v2(kcd, kfe->v1->cageco, se1);
+                               knife_project_v2(kcd, kfe->v2->cageco, se2);
+                               d = dist_to_line_segment_v2(s, se1, se2);
+                               if (d < face_tol) {
+                                       return false;
                                }
                        }
+                       return true;
                }
        }
-
-       if (results)
-               MEM_freeN(results);
-
-       BLI_bvhtree_free(tree2);
-       *count = BLI_array_count(edges);
-
-       return edges;
-}
-
-static void knife_bgl_get_mats(KnifeTool_OpData *UNUSED(kcd), bglMats *mats)
-{
-       bgl_get_mats(mats);
-       //copy_m4_m4(mats->modelview, kcd->vc.rv3d->viewmat);
-       //copy_m4_m4(mats->projection, kcd->vc.rv3d->winmat);
+       return false;
 }
 
 /* Calculate maximum excursion from (0,0,0) of mesh */
@@ -1349,11 +1151,64 @@ static void calc_ortho_extent(KnifeTool_OpData *kcd)
 
        BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
                for (i = 0; i < 3; i++)
-                       max_xyz = max_ff(max_xyz, fabs(v->co[i]));
+                       max_xyz = max_ff(max_xyz, fabsf(v->co[i]));
        }
        kcd->ortho_extent = max_xyz;
 }
 
+/* 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)
+{
+       BMFace *f_hit;
+
+       /* If box clipping on, make sure p is not clipped */
+       if (kcd->vc.rv3d->rflag & RV3D_CLIPPING &&
+           ED_view3d_clipping_test(kcd->vc.rv3d, p, true))
+       {
+               return false;
+       }
+
+       /* If not cutting through, make sure no face is in front of p */
+       if (!kcd->cut_through) {
+               float dist;
+               float view[3], p_ofs[3];
+
+               /* TODO: I think there's a simpler way to get the required raycast ray */
+               ED_view3d_unproject(mats, view, s[0], s[1], 0.0f);
+
+               mul_m4_v3(kcd->ob->imat, view);
+
+               /* 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);
+
+               /* avoid projecting behind the viewpoint */
+               if (kcd->is_ortho && (kcd->vc.rv3d->persp != RV3D_CAMOB)) {
+                       dist = kcd->vc.v3d->far * 2.0f;
+               }
+
+               if (kcd->vc.rv3d->rflag & RV3D_CLIPPING) {
+                       float view_clip[2][3];
+                       /* note: view_clip[0] should never get clipped */
+                       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)) {
+                               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)
+                       return false;
+       }
+
+       return true;
+}
+
 /* 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)
@@ -1366,16 +1221,50 @@ static void clip_to_ortho_planes(float v1[3], float v2[3], float d)
        dist_ensure_v3_v3fl(v2, closest, d);
 }
 
+static void set_linehit_depth(KnifeTool_OpData *kcd, KnifeLineHit *lh)
+{
+       float vnear[3], vfar[3];
+
+       ED_view3d_win_to_segment(kcd->ar, kcd->vc.v3d, lh->schit, vnear, vfar, true);
+       mul_m4_v3(kcd->ob->imat, vnear);
+       if (kcd->is_ortho && (kcd->vc.rv3d->persp != RV3D_CAMOB)) {
+               if (kcd->ortho_extent == 0.0f)
+                       calc_ortho_extent(kcd);
+               clip_to_ortho_planes(vnear, vfar, kcd->ortho_extent + 10.0f);
+       }
+       lh->m = len_v3v3(vnear, lh->cagehit);
+}
+
 /* Finds visible (or all, if cutting through) edges that intersects the current screen drag line */
 static void knife_find_line_hits(KnifeTool_OpData *kcd)
 {
        bglMats mats;
-       BMEdgeHit *e1, *e2;
-       SmallHash hash, *ehash = &hash;
-       float v1[3], v2[3], v3[3], v4[4], s1[2], s2[2];
-       int i, c1, c2;
+       SmallHash faces, kfes, kfvs;
+       float v1[3], v2[3], v3[3], v4[3], s1[2], s2[2];
+       BVHTree *planetree, *tree;
+       BVHTreeOverlap *results, *result;
+       BMLoop **ls;
+       BMFace *f;
+       KnifeEdge *kfe;
+       KnifeVert *v;
+       ListBase *lst;
+       Ref *ref;
+       KnifeLineHit *linehits = NULL;
+       BLI_array_declare(linehits);
+       SmallHashIter hiter;
+       KnifeLineHit hit;
+       void *val;
+       float plane_cos[12];
+       float s[2], se1[2], se2[2], sint[2];
+       float p[3], p2[3], r1[3], r2[3];
+       float d, d1, d2, lambda;
+       float vert_tol, vert_tol_sq, line_tol, face_tol;
+       float eps_scale;
+       int isect_kind;
+       unsigned int tot;
+       int i;
 
-       knife_bgl_get_mats(kcd, &mats);
+       bgl_get_mats(&mats);
 
        if (kcd->linehits) {
                MEM_freeN(kcd->linehits);
@@ -1390,8 +1279,16 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
        knife_project_v2(kcd, v1, s1);
        knife_project_v2(kcd, v2, s2);
 
-       if (len_squared_v2v2(s1, s2) < 1)
-               return;
+       if (kcd->is_interactive) {
+               if (len_squared_v2v2(s1, s2) < 1.0f) {
+                       return;
+               }
+       }
+       else {
+               if (len_squared_v2v2(s1, s2) < KNIFE_FLT_EPS_SQUARED) {
+                       return;
+               }
+       }
 
        /* unproject screen line */
        ED_view3d_win_to_segment(kcd->ar, kcd->vc.v3d, s1, v1, v3, true);
@@ -1404,80 +1301,198 @@ static void knife_find_line_hits(KnifeTool_OpData *kcd)
 
        /* numeric error, 'v1' -> 'v2', 'v2' -> 'v4' can end up being ~2000 units apart in otho mode
         * (from ED_view3d_win_to_segment_clip() above)
-        * this gives precision error in 'knife_edge_tri_isect', rather then solving properly
+        * this gives precision error; rather then solving properly
         * (which may involve using doubles everywhere!),
         * limit the distance between these points */
-       if (kcd->is_ortho) {
+       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);
        }
 
-       BLI_smallhash_init(ehash);
+       /* First use bvh tree to find faces, knife edges, and knife verts that might
+        * intersect the cut plane with rays v1-v3 and v2-v4.
+        * This deduplicates the candidates before doing more expensive intersection tests. */
+
+       tree = BKE_bmbvh_tree_get(kcd->bmbvh);
+       planetree = BLI_bvhtree_new(4, FLT_EPSILON * 4, 8, 8);
+       copy_v3_v3(plane_cos + 0, v1);
+       copy_v3_v3(plane_cos + 3, v2);
+       copy_v3_v3(plane_cos + 6, v3);
+       copy_v3_v3(plane_cos + 9, v4);
+       BLI_bvhtree_insert(planetree, 0, plane_cos, 4);
+       BLI_bvhtree_balance(planetree);
+
+       results = BLI_bvhtree_overlap(tree, planetree, &tot);
+       if (!results) {
+               BLI_bvhtree_free(planetree);
+               return;
+       }
+
+       BLI_smallhash_init(&faces);
+       BLI_smallhash_init(&kfes);
+       BLI_smallhash_init(&kfvs);
+
+       for (i = 0, result = results; i < tot; i++, result++) {
+               ls = (BMLoop **)kcd->em->looptris[result->indexA];
+               f = ls[0]->f;
+               set_lowest_face_tri(kcd, f, result->indexA);
+               /* for faces, store index of lowest hit looptri in hash */
+               if (BLI_smallhash_haskey(&faces, (uintptr_t)f)) {
+                       continue;
+               }
+               /* don't care what the value is except that it is non-NULL, for iterator */
+               BLI_smallhash_insert(&faces, (uintptr_t)f, f);
+
+               lst = knife_get_face_kedges(kcd, f);
+               for (ref = lst->first; ref; ref = ref->next) {
+                       kfe = ref->ref;
+                       if (BLI_smallhash_haskey(&kfes, (uintptr_t)kfe))
+                               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);
+                       v = kfe->v2;
+                       if (!BLI_smallhash_haskey(&kfvs, (uintptr_t)v))
+                               BLI_smallhash_insert(&kfvs, (uintptr_t)v, v);
+               }
+       }
 
-       /* test two triangles of sceen line's plane */
-       e1 = knife_edge_tri_isect(kcd, kcd->bmbvh, v1, v2, v3, ehash, &mats, &c1);
-       e2 = knife_edge_tri_isect(kcd, kcd->bmbvh, v2, v3, v4, ehash, &mats, &c2);
-       if (c1 && c2) {
-               e1 = MEM_reallocN(e1, sizeof(BMEdgeHit) * (c1 + c2));
-               memcpy(e1 + c1, e2, sizeof(BMEdgeHit) * c2);
-               MEM_freeN(e2);
+       /* 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;
+       vert_tol_sq = vert_tol * vert_tol;
+       face_tol = max_ff(vert_tol, line_tol);
+       /* 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))
+       {
+               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->cageco);
+                               copy_v3_v3(hit.cagehit, v->cageco);
+                               copy_v2_v2(hit.schit, s);
+                               set_linehit_depth(kcd, &hit);
+                               BLI_array_append(linehits, hit);
+                       }
+               }
+       }
+       /* now edge hits; don't add if a vertex at end of edge should have hit */
+       for (val = BLI_smallhash_iternew(&kfes, &hiter, (uintptr_t *)&kfe); val;
+            val = BLI_smallhash_iternext(&hiter, (uintptr_t *)&kfe))
+       {
+               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);
+               if (isect_kind == -1) {
+                       /* isect_seg_seg_v2 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) <= vert_tol_sq)
+                               isect_kind = 1;
+                       else {
+                               closest_to_line_segment_v2(sint, s2, se1, se2);
+                               if (len_squared_v2v2(sint, s2) <= vert_tol_sq)
+                                       isect_kind = 1;
+                       }
+               }
+               if (isect_kind == 1) {
+                       d1 = len_v2v2(sint, se1);
+                       d2 = len_v2v2(se2, se1);
+                       if (!(d1 <= vert_tol || d2 <= vert_tol || fabsf(d1 - d2) <= vert_tol)) {
+                               lambda = d1 / d2;
+                               /* Can't just interpolate between ends of kfe because
+                                * that doesn't work with perspective transformation.
+                                * Need to find 3d intersection of ray through sint */
+                               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, p2);
+                               if (isect_kind >= 1 && point_is_visible(kcd, p, sint, &mats)) {
+                                       memset(&hit, 0, sizeof(hit));
+                                       if (kcd->snap_midpoints) {
+                                               /* choose intermediate point snap too */
+                                               mid_v3_v3v3(p, kfe->v1->cageco, kfe->v2->cageco);
+                                               mid_v2_v2v2(sint, se1, se2);
+                                               lambda = 0.5f;
+                                       }
+                                       hit.kfe = kfe;
+                                       copy_v3_v3(hit.hit, p);
+                                       copy_v3_v3(hit.cagehit, p);
+                                       copy_v2_v2(hit.schit, sint);
+                                       hit.perc = lambda;
+                                       set_linehit_depth(kcd, &hit);
+                                       BLI_array_append(linehits, hit);
+                               }
+                       }
+               }
        }
-       else if (c2) {
-               e1 = e2;
+       /* now face hits; don't add if a vertex or edge in face should have hit */
+       for (val = BLI_smallhash_iternew(&faces, &hiter, (uintptr_t *)&f); val;
+            val = BLI_smallhash_iternext(&hiter, (uintptr_t *)&f))
+       {
+               if (knife_ray_intersect_face(kcd, s1, v1, v3, f, face_tol, p)) {
+                       if (point_is_visible(kcd, p, s1, &mats)) {
+                               memset(&hit, 0, sizeof(hit));
+                               hit.f = f;
+                               copy_v3_v3(hit.hit, p);
+                               copy_v3_v3(hit.cagehit, p);
+                               copy_v2_v2(hit.schit, s1);
+                               set_linehit_depth(kcd, &hit);
+                               BLI_array_append(linehits, hit);
+                       }
+               }
+               if (knife_ray_intersect_face(kcd, s2, v2, v4, f, face_tol, p)) {
+                       if (point_is_visible(kcd, p, s2, &mats)) {
+                               memset(&hit, 0, sizeof(hit));
+                               hit.f = f;
+                               copy_v3_v3(hit.hit, p);
+                               copy_v3_v3(hit.cagehit, p);
+                               copy_v2_v2(hit.schit, s2);
+                               set_linehit_depth(kcd, &hit);
+                               BLI_array_append(linehits, hit);
+                       }
+               }
        }
 
-       kcd->linehits = e1;
-       kcd->totlinehit = c1 + c2;
+       kcd->linehits = linehits;
+       kcd->totlinehit = BLI_array_count(linehits);
 
        /* find position along screen line, used for sorting */
        for (i = 0; i < kcd->totlinehit; i++) {
-               BMEdgeHit *lh = e1 + i;
+               KnifeLineHit *lh = kcd->linehits + i;
 
                lh->l = len_v2v2(lh->schit, s1) / len_v2v2(s2, s1);
        }
 
-       BLI_smallhash_release(ehash);
-}
-
-/* this works but gives numeric problems [#33400] */
-#if 0
-static void knife_input_ray_cast(KnifeTool_OpData *kcd, const float mval[2],
-                                 float r_origin[3], float r_ray[3])
-{
-       bglMats mats;
-       float imat[3][3];
-
-       knife_bgl_get_mats(kcd, &mats);
-
-       /* unproject to find view ray */
-       ED_view3d_unproject(&mats, r_origin, mval[0], mval[1], 0.0f);
-
-       if (kcd->is_ortho) {
-               negate_v3_v3(r_ray, kcd->vc.rv3d->viewinv[2]);
-       }
-       else {
-               sub_v3_v3v3(r_ray, r_origin, kcd->vc.rv3d->viewinv[3]);
-       }
-       normalize_v3(r_ray);
-
-       /* transform into object space */
-       invert_m4_m4(kcd->ob->imat, kcd->ob->obmat);
-       copy_m3_m4(imat, kcd->ob->obmat);
-       invert_m3(imat);
-
-       mul_m4_v3(kcd->ob->imat, r_origin);
-       mul_m3_v3(imat, r_ray);
+       BLI_smallhash_release(&faces);
+       BLI_smallhash_release(&kfes);
+       BLI_smallhash_release(&kfvs);
+       BLI_bvhtree_free(planetree);
+       if (results)
+               MEM_freeN(results);
 }
-#endif
 
 static void knife_input_ray_segment(KnifeTool_OpData *kcd, const float mval[2], const float ofs,
                                     float r_origin[3], float r_origin_ofs[3])
 {
        bglMats mats;
 
-       knife_bgl_get_mats(kcd, &mats);
+       bgl_get_mats(&mats);
 
        /* unproject to find view ray */
        ED_view3d_unproject(&mats, r_origin,     mval[0], mval[1], 0.0f);
@@ -1502,7 +1517,7 @@ static BMFace *knife_find_closest_face(KnifeTool_OpData *kcd, float co[3], float
        knife_input_ray_segment(kcd, kcd->curr.mval, 1.0f, origin, origin_ofs);
        sub_v3_v3v3(ray, origin_ofs, origin);
 
-       f = BKE_bmbvh_ray_cast(kcd->bmbvh, origin, ray, NULL, co, cageco);
+       f = BKE_bmbvh_ray_cast(kcd->bmbvh, origin, ray, 0.0f, NULL, co, cageco);
 
        if (is_space)
                *is_space = !f;
@@ -1593,7 +1608,8 @@ static float knife_snap_size(KnifeTool_OpData *kcd, float maxsize)
 }
 
 /* p is closest point on edge to the mouse cursor */
-static KnifeEdge *knife_find_closest_edge(KnifeTool_OpData *kcd, float p[3], float cagep[3], BMFace **fptr, bool *is_space)
+static KnifeEdge *knife_find_closest_edge(KnifeTool_OpData *kcd, float p[3], float cagep[3],
+                                          BMFace **fptr, bool *is_space)
 {
        BMFace *f;
        float co[3], cageco[3], sco[2];
@@ -1614,6 +1630,7 @@ static KnifeEdge *knife_find_closest_edge(KnifeTool_OpData *kcd, float p[3], flo
        if (f) {
                const float maxdist_sq = maxdist * maxdist;
                KnifeEdge *cure = NULL;
+               float cur_cagep[3];
                ListBase *lst;
                Ref *ref;
                float dis_sq, curdis_sq = FLT_MAX;
@@ -1624,35 +1641,62 @@ static KnifeEdge *knife_find_closest_edge(KnifeTool_OpData *kcd, float p[3], flo
                lst = knife_get_face_kedges(kcd, f);
                for (ref = lst->first; ref; ref = ref->next) {
                        KnifeEdge *kfe = ref->ref;
+                       float test_cagep[3];
+                       float lambda;
 
                        /* project edge vertices into screen space */
                        knife_project_v2(kcd, kfe->v1->cageco, kfe->v1->sco);
                        knife_project_v2(kcd, kfe->v2->cageco, kfe->v2->sco);
 
-                       dis_sq = dist_squared_to_line_segment_v2(sco, kfe->v1->sco, kfe->v2->sco);
-                       if (dis_sq < curdis_sq && dis_sq < maxdist_sq) {
-                               if (kcd->vc.rv3d->rflag & RV3D_CLIPPING) {
-                                       float lambda = line_point_factor_v2(sco, kfe->v1->sco, kfe->v2->sco);
-                                       float vec[3];
+                       /* check if we're close enough and calculate 'lambda' */
+                       if (kcd->is_angle_snapping) {
+                       /* if snapping, check we're in bounds */
+                               float sco_snap[2];
+                               isect_line_line_v2_point(kfe->v1->sco, kfe->v2->sco, kcd->prev.mval, kcd->curr.mval, sco_snap);
+                               lambda = line_point_factor_v2(sco_snap, kfe->v1->sco, kfe->v2->sco);
 
-                                       interp_v3_v3v3(vec, kfe->v1->cageco, kfe->v2->cageco, lambda);
+                               /* be strict about angle-snapping within edge */
+                               if ((lambda < 0.0f - KNIFE_FLT_EPSBIG) || (lambda > 1.0f + KNIFE_FLT_EPSBIG)) {
+                                       continue;
+                               }
 
-                                       if (ED_view3d_clipping_test(kcd->vc.rv3d, vec, true) == 0) {
-                                               cure = kfe;
-                                               curdis_sq = dis_sq;
-                                       }
+                               dis_sq = len_squared_v2v2(sco, sco_snap);
+                               if (dis_sq < curdis_sq && dis_sq < maxdist_sq) {
+                                       /* we already have 'lambda' */
+                               }
+                               else {
+                                       continue;
+                               }
+                       }
+                       else {
+                               dis_sq = dist_squared_to_line_segment_v2(sco, kfe->v1->sco, kfe->v2->sco);
+                               if (dis_sq < curdis_sq && dis_sq < maxdist_sq) {
+                                       lambda = line_point_factor_v2(sco, kfe->v1->sco, kfe->v2->sco);
                                }
                                else {
-                                       cure = kfe;
-                                       curdis_sq = dis_sq;
+                                       continue;
+                               }
+                       }
+
+                       /* now we have 'lambda' calculated */
+                       interp_v3_v3v3(test_cagep, kfe->v1->cageco, kfe->v2->cageco, lambda);
+
+                       if (kcd->vc.rv3d->rflag & RV3D_CLIPPING) {
+                               /* check we're in the view */
+                               if (ED_view3d_clipping_test(kcd->vc.rv3d, test_cagep, true)) {
+                                       continue;
                                }
                        }
+
+                       cure = kfe;
+                       curdis_sq = dis_sq;
+                       copy_v3_v3(cur_cagep, test_cagep);
                }
 
                if (fptr)
                        *fptr = f;
 
-               if (cure && p) {
+               if (cure) {
                        if (!kcd->ignore_edge_snapping || !(cure->e)) {
                                KnifeVert *edgesnap = NULL;
 
@@ -1661,11 +1705,9 @@ static KnifeEdge *knife_find_closest_edge(KnifeTool_OpData *kcd, float p[3], flo
                                        mid_v3_v3v3(cagep, cure->v1->cageco, cure->v2->cageco);
                                }
                                else {
-                                       float d;
-
-                                       closest_to_line_segment_v3(cagep, cageco, cure->v1->cageco, cure->v2->cageco);
-                                       d = len_v3v3(cagep, cure->v1->cageco) / len_v3v3(cure->v1->cageco, cure->v2->cageco);
-                                       interp_v3_v3v3(p, cure->v1->co, cure->v2->co, d);
+                                       float lambda = line_point_factor_v3(cur_cagep, cure->v1->cageco, cure->v2->cageco);
+                                       copy_v3_v3(cagep, cur_cagep);
+                                       interp_v3_v3v3(p, cure->v1->co, cure->v2->co, lambda);
                                }
 
                                /* update mouse coordinates to the snapped-to edge's screen coordinates
@@ -1703,7 +1745,7 @@ static KnifeVert *knife_find_closest_vert(KnifeTool_OpData *kcd, float p[3], flo
 
        /* set p to co, in case we don't find anything, means a face cut */
        copy_v3_v3(p, co);
-       copy_v3_v3(cagep, p);
+       copy_v3_v3(cagep, cageco);
        kcd->curr.bmface = f;
 
        if (f) {
@@ -1725,6 +1767,13 @@ static KnifeVert *knife_find_closest_vert(KnifeTool_OpData *kcd, float p[3], flo
 
                                knife_project_v2(kcd, kfv->cageco, kfv->sco);
 
+                               /* be strict about angle snapping, the vertex needs to be very close to the angle, or we ignore */
+                               if (kcd->is_angle_snapping) {
+                                       if (dist_squared_to_line_segment_v2(kfv->sco, kcd->prev.mval, kcd->curr.mval) > KNIFE_FLT_EPSBIG) {
+                                               continue;
+                                       }
+                               }
+
                                dis_sq = len_squared_v2v2(kfv->sco, sco);
                                if (dis_sq < curdis_sq && dis_sq < maxdist_sq) {
                                        if (kcd->vc.rv3d->rflag & RV3D_CLIPPING) {
@@ -1745,7 +1794,7 @@ static KnifeVert *knife_find_closest_vert(KnifeTool_OpData *kcd, float p[3], flo
                        if (fptr)
                                *fptr = f;
 
-                       if (curv && p) {
+                       if (curv) {
                                copy_v3_v3(p, curv->co);
                                copy_v3_v3(cagep, curv->cageco);
 
@@ -1772,7 +1821,7 @@ static KnifeVert *knife_find_closest_vert(KnifeTool_OpData *kcd, float p[3], flo
 }
 
 /* update both kcd->curr.mval and kcd->mval to snap to required angle */
-static void knife_snap_angle(KnifeTool_OpData *kcd)
+static bool knife_snap_angle(KnifeTool_OpData *kcd)
 {
        float dx, dy;
        float w, abs_tan;
@@ -1780,7 +1829,7 @@ static void knife_snap_angle(KnifeTool_OpData *kcd)
        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)
-               return;
+               return false;
 
        if (dx == 0.0f) {
                kcd->angle_snapping = ANGLE_90;
@@ -1809,6 +1858,8 @@ static void knife_snap_angle(KnifeTool_OpData *kcd)
        }
 
        copy_v2_v2(kcd->mval, kcd->curr.mval);
+
+       return true;
 }
 
 /* update active knife edge/vert pointers */
@@ -1816,29 +1867,36 @@ static int knife_update_active(KnifeTool_OpData *kcd)
 {
        knife_pos_data_clear(&kcd->curr);
        copy_v2_v2(kcd->curr.mval, kcd->mval);
-       if (kcd->angle_snapping != ANGLE_FREE && kcd->mode == MODE_DRAGGING)
-               knife_snap_angle(kcd);
 
-       /* XXX knife_snap_angle updates the view coordinate mouse values to constrained angles,
-        * which current mouse values are set to current mouse values are then used
-        * for vertex and edge snap detection, without regard to the exact angle constraint */
+       /* view matrix may have changed, reproject */
+       knife_project_v2(kcd, kcd->prev.co, kcd->prev.mval);
+
+       if (kcd->angle_snapping != ANGLE_FREE && kcd->mode == MODE_DRAGGING) {
+               kcd->is_angle_snapping = knife_snap_angle(kcd);
+       }
+       else {
+               kcd->is_angle_snapping = false;
+       }
+
        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) {
-               kcd->curr.edge = knife_find_closest_edge(kcd, kcd->curr.co, kcd->curr.cage, &kcd->curr.bmface, &kcd->curr.is_space);
+               kcd->curr.edge = knife_find_closest_edge(kcd, kcd->curr.co, kcd->curr.cage,
+                                                        &kcd->curr.bmface, &kcd->curr.is_space);
        }
 
        /* if no hits are found this would normally default to (0, 0, 0) so instead
         * get a point at the mouse ray closest to the previous point.
         * Note that drawing lines in `free-space` isn't properly supported
         * but theres no guarantee (0, 0, 0) has any geometry either - campbell */
-       if (kcd->curr.vert == NULL && kcd->curr.edge == NULL) {
+       if (kcd->curr.vert == NULL && kcd->curr.edge == NULL && kcd->curr.bmface == NULL) {
                float origin[3];
                float origin_ofs[3];
 
                knife_input_ray_segment(kcd, kcd->curr.mval, 1.0f, origin, origin_ofs);
 
                closest_to_line_v3(kcd->curr.cage, kcd->prev.cage, origin_ofs, origin);
+               copy_v3_v3(kcd->curr.co, kcd->curr.cage);
        }
 
        if (kcd->mode == MODE_DRAGGING) {
@@ -1847,380 +1905,11 @@ static int knife_update_active(KnifeTool_OpData *kcd)
        return 1;
 }
 
-#define SCANFILL_CUTS 0
-#if SCANFILL_CUTS
-
-#define MARK            4
-#define DEL             8
-#define VERT_ON_EDGE    16
-#define VERT_ORIG       32
-#define FACE_FLIP       64
-#define BOUNDARY        128
-#define FACE_NEW        256
-
-typedef struct facenet_entry {
-       struct facenet_entry *next, *prev;
-       KnifeEdge *kfe;
-} facenet_entry;
-
-static void rnd_offset_co(RNG *rng, float co[3], float scale)
-{
-       int i;
-
-       for (i = 0; i < 3; i++) {
-               co[i] += (BLI_rng_get_float(rng) - 0.5) * scale;
-       }
-}
-
-static void remerge_faces(KnifeTool_OpData *kcd)
-{
-       BMesh *bm = kcd->em->bm;
-       SmallHash svisit, *visit = &svisit;
-       BMIter iter;
-       BMFace *f;
-       BMFace **stack = NULL;
-       BLI_array_declare(stack);
-       BMFace **faces = NULL;
-       BLI_array_declare(faces);
-       BMOperator bmop;
-       int idx;
-
-       BMO_op_initf(bm, &bmop, "beautify_fill faces=%ff edges=%Fe", FACE_NEW, BOUNDARY);
-
-       BMO_op_exec(bm, &bmop);
-       BMO_slot_buffer_flag_enable(bm, &bmop, "geom.out", BM_FACE, FACE_NEW);
-
-       BMO_op_finish(bm, &bmop);
-
-       BLI_smallhash_init(visit);
-       BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
-               BMIter eiter;
-               BMEdge *e;
-               BMFace *f2;
-
-               if (!BMO_elem_flag_test(bm, f, FACE_NEW))
-                       continue;
-
-               if (BLI_smallhash_haskey(visit, (uintptr_t)f))
-                       continue;
-
-               BLI_array_empty(stack);
-               BLI_array_empty(faces);
-               BLI_array_append(stack, f);
-               BLI_smallhash_insert(visit, (uintptr_t)f, NULL);
-
-               do {
-                       f2 = BLI_array_pop(stack);
-
-                       BLI_array_append(faces, f2);
-
-                       BM_ITER_ELEM (e, &eiter, f2, BM_EDGES_OF_FACE) {
-                               BMIter fiter;
-                               BMFace *f3;
-
-                               if (BMO_elem_flag_test(bm, e, BOUNDARY))
-                                       continue;
-
-                               BM_ITER_ELEM (f3, &fiter, e, BM_FACES_OF_EDGE) {
-                                       if (!BMO_elem_flag_test(bm, f3, FACE_NEW))
-                                               continue;
-                                       if (BLI_smallhash_haskey(visit, (uintptr_t)f3))
-                                               continue;
-
-                                       BLI_smallhash_insert(visit, (uintptr_t)f3, NULL);
-                                       BLI_array_append(stack, f3);
-                               }
-                       }
-               } while (BLI_array_count(stack) > 0);
-
-               if (BLI_array_count(faces) > 0) {
-                       idx = BM_elem_index_get(faces[0]);
-
-                       f2 = BM_faces_join(bm, faces, BLI_array_count(faces), true);
-                       if (f2) {
-                               BMO_elem_flag_enable(bm, f2, FACE_NEW);
-                               BM_elem_index_set(f2, idx); /* set_dirty! *//* BMESH_TODO, check if this is valid or not */
-                       }
-               }
-       }
-       /* BMESH_TODO, check if the code above validates the indices */
-       /* bm->elem_index_dirty &= ~BM_FACE; */
-       bm->elem_index_dirty |= BM_FACE;
-
-       BLI_smallhash_release(visit);
-
-       BLI_array_free(stack);
-       BLI_array_free(faces);
-}
-
-/* use edgenet to fill faces.  this is a bit annoying and convoluted.*/
-static void knifenet_fill_faces(KnifeTool_OpData *kcd)
-{
-       ScanFillContext sf_ctx;
-       BMesh *bm = kcd->em->bm;
-       BMIter bmiter;
-       BLI_mempool_iter iter;
-       BMFace *f;
-       BMEdge *e;
-       KnifeVert *kfv;
-       KnifeEdge *kfe;
-       facenet_entry *entry;
-       ListBase *face_nets = MEM_callocN(sizeof(ListBase) * bm->totface, "face_nets");
-       BMFace **faces = MEM_callocN(sizeof(BMFace *) * bm->totface, "faces knife");
-       MemArena *arena = BLI_memarena_new(1 << 16, "knifenet_fill_faces");
-       SmallHash shash;
-       RNG *rng;
-       int i, j, k = 0, totface = bm->totface;
-
-       BMO_push(bm, NULL);
-       bmesh_edit_begin(bm, BMO_OPTYPE_FLAG_UNTAN_MULTIRES | BMO_OPTYPE_FLAG_NORMALS_CALC | BMO_OPTYPE_FLAG_SELECT_FLUSH);
-
-       /* BMESH_TODO this should be valid now, leaving here until we can ensure this - campbell */
-       i = 0;
-       BM_ITER_MESH (f, &bmiter, bm, BM_FACES_OF_MESH) {
-               BM_elem_index_set(f, i); /* set_inline */
-               faces[i] = f;
-               i++;
-       }
-       bm->elem_index_dirty &= ~BM_FACE;
-
-       BM_ITER_MESH (e, &bmiter, bm, BM_EDGES_OF_MESH) {
-               BMO_elem_flag_enable(bm, e, BOUNDARY);
-       }
-
-       /* turn knife verts into real verts, as necessary */
-       BLI_mempool_iternew(kcd->kverts, &iter);
-       for (kfv = BLI_mempool_iterstep(&iter); kfv; kfv = BLI_mempool_iterstep(&iter)) {
-               if (!kfv->v) {
-                       /* shouldn't we be at least copying the normal? - if not some comment here should explain why - campbell */
-                       kfv->v = BM_vert_create(bm, kfv->co, NULL);
-                       kfv->flag = 1;
-                       BMO_elem_flag_enable(bm, kfv->v, DEL);
-               }
-               else {
-                       kfv->flag = 0;
-                       BMO_elem_flag_enable(bm, kfv->v, VERT_ORIG);
-               }
-
-               BMO_elem_flag_enable(bm, kfv->v, MARK);
-       }
-
-       /* we want to only do changed faces.  first, go over new edges and add to
-        * face net lists.*/
-       i = j = k = 0;
-       BLI_mempool_iternew(kcd->kedges, &iter);
-       for (kfe = BLI_mempool_iterstep(&iter); kfe; kfe = BLI_mempool_iterstep(&iter)) {
-               Ref *ref;
-               if (!kfe->v1 || !kfe->v2 || kfe->v1->inspace || kfe->v2->inspace)
-                       continue;
-
-               i++;
-
-               if (kfe->e && kfe->v1->v == kfe->e->v1 && kfe->v2->v == kfe->e->v2) {
-                       kfe->e_old = kfe->e;
-                       continue;
-               }
-
-               j++;
-
-               if (kfe->e) {
-                       kfe->e_old = kfe->e;
-
-                       BMO_elem_flag_enable(bm, kfe->e, DEL);
-                       BMO_elem_flag_disable(bm, kfe->e, BOUNDARY);
-                       kfe->e = NULL;
-               }
-
-               kfe->e = BM_edge_create(bm, kfe->v1->v, kfe->v2->v, NULL, BM_CREATE_NO_DOUBLE);
-               BMO_elem_flag_enable(bm, kfe->e, BOUNDARY);
-
-               for (ref = kfe->faces.first; ref; ref = ref->next) {
-                       f = ref->ref;
-
-                       entry = BLI_memarena_alloc(arena, sizeof(*entry));
-                       entry->kfe = kfe;
-                       BLI_addtail(face_nets + BM_elem_index_get(f), entry);
-               }
-       }
-
-       /* go over original edges, and add to faces with new geometry */
-       BLI_mempool_iternew(kcd->kedges, &iter);
-       for (kfe = BLI_mempool_iterstep(&iter); kfe; kfe = BLI_mempool_iterstep(&iter)) {
-               Ref *ref;
-
-               if (!kfe->v1 || !kfe->v2 || kfe->v1->inspace || kfe->v2->inspace)
-                       continue;
-               if (!(kfe->e_old && kfe->v1->v == kfe->e_old->v1 && kfe->v2->v == kfe->e_old->v2))
-                       continue;
-
-               k++;
-
-               BMO_elem_flag_enable(bm, kfe->e, BOUNDARY);
-               kfe->e_old = kfe->e;
-
-               for (ref = kfe->faces.first; ref; ref = ref->next) {
-                       f = ref->ref;
-
-                       if (face_nets[BM_elem_index_get(f)].first) {
-                               entry = BLI_memarena_alloc(arena, sizeof(*entry));
-                               entry->kfe = kfe;
-                               BLI_addtail(face_nets + BM_elem_index_get(f), entry);
-                       }
-               }
-       }
-
-       rng = BLI_rng_new(0);
-
-       for (i = 0; i < totface; i++) {
-               SmallHash *hash = &shash;
-               ScanFillFace *sf_tri;
-               ScanFillVert *sf_vert, *sf_vert_last;
-               int j;
-               float rndscale = (KNIFE_FLT_EPS / 4.0f);
-
-               f = faces[i];
-               BLI_smallhash_init(hash);
-
-               if (face_nets[i].first)
-                       BMO_elem_flag_enable(bm, f, DEL);
-
-               BLI_scanfill_begin(&sf_ctx);
-
-               for (entry = face_nets[i].first; entry; entry = entry->next) {
-                       if (!BLI_smallhash_haskey(hash, (uintptr_t)entry->kfe->v1)) {
-                               sf_vert = BLI_scanfill_vert_add(&sf_ctx, entry->kfe->v1->v->co);
-                               sf_vert->poly_nr = 0;
-                               rnd_offset_co(rng, sf_vert->co, rndscale);
-                               sf_vert->tmp.p = entry->kfe->v1->v;
-                               BLI_smallhash_insert(hash, (uintptr_t)entry->kfe->v1, sf_vert);
-                       }
-
-                       if (!BLI_smallhash_haskey(hash, (uintptr_t)entry->kfe->v2)) {
-                               sf_vert = BLI_scanfill_vert_add(&sf_ctx, entry->kfe->v2->v->co);
-                               sf_vert->poly_nr = 0;
-                               rnd_offset_co(rng, sf_vert->co, rndscale);
-                               sf_vert->tmp.p = entry->kfe->v2->v;
-                               BLI_smallhash_insert(hash, (uintptr_t)entry->kfe->v2, sf_vert);
-                       }
-               }
-
-               for (j = 0, entry = face_nets[i].first; entry; entry = entry->next, j++) {
-                       sf_vert_last = BLI_smallhash_lookup(hash, (uintptr_t)entry->kfe->v1);
-                       sf_vert = BLI_smallhash_lookup(hash, (uintptr_t)entry->kfe->v2);
-
-                       sf_vert->poly_nr++;
-                       sf_vert_last->poly_nr++;
-               }
-
-               for (j = 0, entry = face_nets[i].first; entry; entry = entry->next, j++) {
-                       sf_vert_last = BLI_smallhash_lookup(hash, (uintptr_t)entry->kfe->v1);
-                       sf_vert = BLI_smallhash_lookup(hash, (uintptr_t)entry->kfe->v2);
-
-                       if (sf_vert->poly_nr > 1 && sf_vert_last->poly_nr > 1) {
-                               ScanFillEdge *sf_edge;
-                               sf_edge = BLI_scanfill_edge_add(&sf_ctx, sf_vert_last, sf_vert);
-                               if (entry->kfe->e_old)
-                                       sf_edge->f = SF_EDGE_BOUNDARY;  /* mark as original boundary edge */
-
-                               BMO_elem_flag_disable(bm, entry->kfe->e->v1, DEL);
-                               BMO_elem_flag_disable(bm, entry->kfe->e->v2, DEL);
-                       }
-                       else {
-                               if (sf_vert_last->poly_nr < 2)
-                                       BLI_remlink(&sf_ctx.fillvertbase, sf_vert_last);
-                               if (sf_vert->poly_nr < 2)
-                                       BLI_remlink(&sf_ctx.fillvertbase, sf_vert);
-                       }
-               }
-
-               BLI_scanfill_calc(&sf_ctx, 0);
-
-               for (sf_tri = sf_ctx.fillfacebase.first; sf_tri; sf_tri = sf_tri->next) {
-                       BMVert *v1 = sf_tri->v3->tmp.p, *v2 = sf_tri->v2->tmp.p, *v3 = sf_tri->v1->tmp.p;
-                       BMFace *f2;
-                       BMLoop *l_iter;
-                       BMVert *verts[3] = {v1, v2, v3};
-
-                       if (v1 == v2 || v2 == v3 || v1 == v3)
-                               continue;
-                       if (BM_face_exists(bm, verts, 3, &f2))
-                               continue;
-
-                       f2 = BM_face_create_quad_tri(bm,
-                                                    v1, v2, v3, NULL,
-                                                    NULL, false);
-
-                       BMO_elem_flag_enable(bm, f2, FACE_NEW);
-
-                       l_iter = BM_FACE_FIRST_LOOP(f2);
-                       do {
-                               BMO_elem_flag_disable(bm, l_iter->e, DEL);
-                       } while ((l_iter = l_iter->next) != BM_FACE_FIRST_LOOP(f2));
-
-                       BMO_elem_flag_disable(bm, f2, DEL);
-                       BM_elem_index_set(f2, i); /* set_dirty! *//* note, not 100% sure this is dirty? need to check */
-
-                       BM_face_normal_update(f2);
-                       if (dot_v3v3(f->no, f2->no) < 0.0f) {
-                               BM_face_normal_flip(bm, f2);
-                       }
-               }
-
-               BLI_scanfill_end(&sf_ctx);
-               BLI_smallhash_release(hash);
-       }
-       bm->elem_index_dirty |= BM_FACE;
-
-       /* interpolate customdata */
-       BM_ITER_MESH (f, &bmiter, bm, BM_FACES_OF_MESH) {
-               BMLoop *l1;
-               BMFace *f2;
-               BMIter liter1;
-
-               if (!BMO_elem_flag_test(bm, f, FACE_NEW))
-                       continue;
-
-               f2 = faces[BM_elem_index_get(f)];
-               if (BM_elem_index_get(f) < 0 || BM_elem_index_get(f) >= totface) {
-                       fprintf(stderr, "%s: face index out of range! (bmesh internal error)\n", __func__);
-               }
-
-               BM_elem_attrs_copy(bm, bm, f2, f);
-
-               BM_ITER_ELEM (l1, &liter1, f, BM_LOOPS_OF_FACE) {
-                       BM_loop_interp_from_face(bm, l1, f2, true, true);
-               }
-       }
-
-       /* merge triangles back into faces */
-       remerge_faces(kcd);
-
-       /* delete left over faces */
-       BMO_op_callf(bm, BMO_FLAG_DEFAULTS, "delete geom=%ff context=%i", DEL, DEL_ONLYFACES);
-       BMO_op_callf(bm, BMO_FLAG_DEFAULTS, "delete geom=%fe context=%i", DEL, DEL_EDGES);
-       BMO_op_callf(bm, BMO_FLAG_DEFAULTS, "delete geom=%fv context=%i", DEL, DEL_VERTS);
-
-       if (face_nets) 
-               MEM_freeN(face_nets);
-       if (faces)
-               MEM_freeN(faces);
-       BLI_memarena_free(arena);
-       BLI_rng_free(rng);
-
-       BMO_error_clear(bm); /* remerge_faces sometimes raises errors, so make sure to clear them */
-
-       bmesh_edit_end(bm, BMO_OPTYPE_FLAG_UNTAN_MULTIRES | BMO_OPTYPE_FLAG_NORMALS_CALC | BMO_OPTYPE_FLAG_SELECT_FLUSH);
-       BMO_pop(bm);
-}
-
-#else  /* use direct (non-scanfill) method for cuts */
-
 /* sort list of kverts by fraction along edge e */
 static void sort_by_frac_along(ListBase *lst, BMEdge *e)
 {
        /* note, since we know the point is along the edge, sort from distance to v1co */
        const float *v1co = e->v1->co;
-//     const float *v2co = e->v2->co;
        Ref *cur = NULL, *prev = NULL, *next = NULL;
 
        if (lst->first == lst->last)
@@ -2228,11 +1917,7 @@ static void sort_by_frac_along(ListBase *lst, BMEdge *e)
 
        for (cur = ((Ref *)lst->first)->next; cur; cur = next) {
                KnifeVert *vcur = cur->ref;
-#if 0
-               const float vcur_fac = line_point_factor_v3(vcur->co, v1co, v2co);
-#else
-               const float vcur_fac = len_squared_v3v3(v1co, vcur->co);
-#endif
+               const float vcur_fac_sq = len_squared_v3v3(v1co, vcur->co);
 
                next = cur->next;
                prev = cur->prev;
@@ -2241,13 +1926,8 @@ static void sort_by_frac_along(ListBase *lst, BMEdge *e)
 
                while (prev) {
                        KnifeVert *vprev = prev->ref;
-#if 0
-                       if (line_point_factor_v3(vprev->co, v1co, v2co) <= vcur_fac)
-                               break;
-#else
-                       if (len_squared_v3v3(v1co, vprev->co) <= vcur_fac)
+                       if (len_squared_v3v3(v1co, vprev->co) <= vcur_fac_sq)
                                break;
-#endif
                        prev = prev->prev;
                }
 
@@ -2452,7 +2132,7 @@ static bool find_hole_chains(KnifeTool_OpData *kcd, ListBase *hole, BMFace *f, L
        BMIter iter;
        int nh, nf, i, j, k, m, ax, ay, sep = 0 /* Quite warnings */, bestsep;
        int besti[2], bestj[2];
-       float d, bestd;
+       float dist_sq, dist_best_sq;
 
        nh = BLI_countlist(hole);
        nf = f->len;
@@ -2505,7 +2185,7 @@ static bool find_hole_chains(KnifeTool_OpData *kcd, ListBase *hole, BMFace *f, L
        for (m = 0; m < 2; m++) {
                besti[m] = -1;
                bestj[m] = -1;
-               bestd = FLT_MAX;
+               dist_best_sq = FLT_MAX;
                bestsep = 0;
                for (i = 0; i < nh; i++) {
                        if (m == 1) {
@@ -2515,15 +2195,15 @@ static bool find_hole_chains(KnifeTool_OpData *kcd, ListBase *hole, BMFace *f, L
                                sep = MIN2(sep, nh - sep);
                                if (sep < bestsep)
                                        continue;
-                               bestd = FLT_MAX;
+                               dist_best_sq = FLT_MAX;
                        }
                        for (j = 0; j < nf; j++) {
                                bool ok;
 
                                if (m == 1 && j == bestj[0])
                                        continue;
-                               d = len_squared_v2v2(hco[i], fco[j]);
-                               if (d > bestd)
+                               dist_sq = len_squared_v2v2(hco[i], fco[j]);
+                               if (dist_sq > dist_best_sq)
                                        continue;
 
                                ok = true;
@@ -2546,7 +2226,7 @@ static bool find_hole_chains(KnifeTool_OpData *kcd, ListBase *hole, BMFace *f, L
                                        bestj[m] = j;
                                        if (m == 1)
                                                bestsep = sep;
-                                       bestd = d;
+                                       dist_best_sq = dist_sq;
                                }
                        }
                }
@@ -2581,47 +2261,48 @@ static bool find_hole_chains(KnifeTool_OpData *kcd, ListBase *hole, BMFace *f, L
        }
 }
 
-static bool knife_edge_in_face(KnifeTool_OpData *UNUSED(kcd), KnifeEdge *kfe, BMFace *f)
+static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f)
 {
-       /* BMesh *bm = kcd->em->bm; */ /* UNUSED */
-       BMVert *v1, *v2;
        BMLoop *l1, *l2, *l;
        float mid[3];
        BMIter iter;
        int v1inside, v2inside;
 
-       if (!f)
+       if (!f || !v1 || !v2)
                return false;
 
-       v1 = kfe->v1->v;
-       v2 = kfe->v2->v;
        l1 = NULL;
        l2 = NULL;
 
        /* find out if v1 and v2, if set, are part of the face */
        BM_ITER_ELEM (l, &iter, f, BM_LOOPS_OF_FACE) {
-               if (v1 && l->v == v1)
+               if (v1->v && l->v == v1->v)
                        l1 = l;
-               if (v2 && l->v == v2)
+               if (v2->v && l->v == v2->v)
                        l2 = l;
        }
 
        /* BM_face_point_inside_test uses best-axis projection so this isn't most accurate test... */
-       v1inside = l1 ? 0 : BM_face_point_inside_test(f, kfe->v1->co);
-       v2inside = l2 ? 0 : BM_face_point_inside_test(f, kfe->v2->co);
+       v1inside = l1 ? 0 : BM_face_point_inside_test(f, v1->co);
+       v2inside = l2 ? 0 : BM_face_point_inside_test(f, v2->co);
        if ((l1 && v2inside) || (l2 && v1inside) || (v1inside && v2inside))
                return true;
        if (l1 && l2) {
                /* Can have case where v1 and v2 are on shared chain between two faces.
-                * BM_face_legal_splits does visibility and self-intersection tests,
+                * BM_face_splits_check_legal does visibility and self-intersection tests,
                 * but it is expensive and maybe a bit buggy, so use a simple
                 * "is the midpoint in the face" test */
-               mid_v3_v3v3(mid, kfe->v1->co, kfe->v2->co);
+               mid_v3_v3v3(mid, v1->co, v2->co);
                return BM_face_point_inside_test(f, mid);
        }
        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)
@@ -2629,6 +2310,7 @@ static void knife_make_chain_cut(KnifeTool_OpData *kcd, BMFace *f, ListBase *cha
        BMesh *bm = kcd->em->bm;
        KnifeEdge *kfe, *kfelast;
        BMVert *v1, *v2;
+       BMLoop *l_v1, *l_v2;
        BMFace *f_new;
        Ref *ref;
        KnifeVert *kfv, *kfvprev;
@@ -2654,28 +2336,36 @@ static void knife_make_chain_cut(KnifeTool_OpData *kcd, BMFace *f, ListBase *cha
        }
        BLI_assert(i == nco);
        l_new = NULL;
-       if (nco == 0) {
-               /* Want to prevent creating two-sided polygons */
-               if (BM_edge_exists(v1, v2)) {
-                       f_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;
+                       }
+                       else {
+                               f_new = BM_face_split(bm, f, l_v1, l_v2, &l_new, NULL, true);
+                       }
                }
                else {
-                       f_new = BM_face_split(bm, f, v1, v2, &l_new, NULL, true);
-               }
-       }
-       else {
-               f_new = BM_face_split_n(bm, f, v1, 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);
+                       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;
                                }
-                               kverts[i]->v = l_iter->v;
                        }
                }
        }
+       else {
+               f_new = NULL;
+       }
 
        /* the select chain above doesnt account for the first loop */
        if (kcd->select_result) {
@@ -2683,7 +2373,7 @@ static void knife_make_chain_cut(KnifeTool_OpData *kcd, BMFace *f, ListBase *cha
                        BM_edge_select_set(bm, l_new->e, true);
                }
        }
-       else {
+       else if (f_new) {
                BM_elem_select_copy(bm, bm, f_new, f);
        }
 
@@ -2713,7 +2403,7 @@ static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfe
                for (ref = kfedges->first; ref; ref = refnext) {
                        kfe = ref->ref;
                        refnext = ref->next;
-                       if (knife_edge_in_face(kcd, kfe, fnew)) {
+                       if (knife_edge_in_face(kfe, fnew)) {
                                BLI_remlink(kfedges, ref);
                                kfe->basef = fnew;
                                knife_append_list(kcd, fnew_kfedges, kfe);
@@ -2743,14 +2433,14 @@ static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfe
                                return;
                        }
                        kfe = ((Ref *)sidechain->first)->ref;
-                       if (knife_edge_in_face(kcd, kfe, f)) {
+                       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(kcd, kfe, fnew)) {
+                       else if (knife_edge_in_face(kfe, fnew)) {
                                knife_make_chain_cut(kcd, fnew, sidechain, &fnew2);
                                if (fnew2 == NULL) {
                                        return;
@@ -2769,12 +2459,12 @@ static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfe
                        for (ref = kfedges->first; ref; ref = refnext) {
                                kfe = ref->ref;
                                refnext = ref->next;
-                               if (knife_edge_in_face(kcd, kfe, fnew)) {
+                               if (knife_edge_in_face(kfe, fnew)) {
                                        BLI_remlink(kfedges, ref);
                                        kfe->basef = fnew;
                                        knife_append_list(kcd, fnew_kfedges, kfe);
                                }
-                               else if (knife_edge_in_face(kcd, kfe, fnew2)) {
+                               else if (knife_edge_in_face(kfe, fnew2)) {
                                        BLI_remlink(kfedges, ref);
                                        kfe->basef = fnew2;
                                        knife_append_list(kcd, fnew2_kfedges, kfe);
@@ -2880,16 +2570,11 @@ static void knife_make_cuts(KnifeTool_OpData *kcd)
        BLI_smallhash_release(fhash);
        BLI_smallhash_release(ehash);
 }
-#endif
 
 /* called on tool confirmation */
 static void knifetool_finish_ex(KnifeTool_OpData *kcd)
 {
-#if SCANFILL_CUTS
-       knifenet_fill_faces(kcd);
-#else
        knife_make_cuts(kcd);
-#endif
 
        EDBM_selectmode_flush(kcd->em);
        EDBM_mesh_normals_update(kcd->em);
@@ -2918,7 +2603,7 @@ static void knifetool_exit_ex(bContext *C, KnifeTool_OpData *kcd)
                return;
 
        if (kcd->is_interactive) {
-               WM_cursor_restore(CTX_wm_window(C));
+               WM_cursor_modal_restore(CTX_wm_window(C));
 
                /* deactivate the extra drawing stuff in 3D-View */
                ED_region_draw_cb_exit(kcd->ar->type, kcd->draw_handle);
@@ -2932,6 +2617,7 @@ static void knifetool_exit_ex(bContext *C, KnifeTool_OpData *kcd)
        BLI_ghash_free(kcd->origedgemap, NULL, NULL);
        BLI_ghash_free(kcd->origvertmap, NULL, NULL);
        BLI_ghash_free(kcd->kedgefacemap, NULL, NULL);
+       BLI_ghash_free(kcd->facetrimap, NULL, NULL);
 
        BKE_bmbvh_free(kcd->bmbvh);
        BLI_memarena_free(kcd->arena);
@@ -2990,28 +2676,27 @@ static void knifetool_init(bContext *C, KnifeTool_OpData *kcd,
 
        kcd->cagecos = (const float (*)[3])BKE_editmesh_vertexCos_get(kcd->em, scene, NULL);
 
-       kcd->bmbvh = BKE_bmbvh_new(kcd->em,
-                                 BMBVH_RETURN_ORIG |
-                                 (only_select ? BMBVH_RESPECT_SELECT : BMBVH_RESPECT_HIDDEN),
-                                 kcd->cagecos, false);
+       kcd->bmbvh = BKE_bmbvh_new_from_editmesh(kcd->em,
+                                                BMBVH_RETURN_ORIG |
+                                                (only_select ? BMBVH_RESPECT_SELECT : BMBVH_RESPECT_HIDDEN),
+                                                kcd->cagecos, false);
 
-       kcd->arena = BLI_memarena_new(1 << 15, "knife");
+       kcd->arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 15), "knife");
        kcd->vthresh = KMAXDIST - 1;
        kcd->ethresh = KMAXDIST;
 
-       kcd->extend = true;
-
        knife_recalc_projmat(kcd);
 
        ED_region_tag_redraw(kcd->ar);
 
-       kcd->refs = BLI_mempool_create(sizeof(Ref), 1, 2048, 0);
-       kcd->kverts = BLI_mempool_create(sizeof(KnifeVert), 1, 512, BLI_MEMPOOL_ALLOW_ITER);
-       kcd->kedges = BLI_mempool_create(sizeof(KnifeEdge), 1, 512, BLI_MEMPOOL_ALLOW_ITER);
+       kcd->refs = BLI_mempool_create(sizeof(Ref), 0, 2048, 0);
+       kcd->kverts = BLI_mempool_create(sizeof(KnifeVert), 0, 512, BLI_MEMPOOL_ALLOW_ITER);
+       kcd->kedges = BLI_mempool_create(sizeof(KnifeEdge), 0, 512, BLI_MEMPOOL_ALLOW_ITER);
 
        kcd->origedgemap = BLI_ghash_ptr_new("knife origedgemap");
        kcd->origvertmap = BLI_ghash_ptr_new("knife origvertmap");
-       kcd->kedgefacemap = BLI_ghash_ptr_new("knife origvertmap");
+       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;
@@ -3031,11 +2716,10 @@ static void knifetool_init(bContext *C, KnifeTool_OpData *kcd,
        }
 }
 
-static int knifetool_cancel(bContext *C, wmOperator *op)
+static void knifetool_cancel(bContext *C, wmOperator *op)
 {
        /* this is just a wrapper around exit() */
        knifetool_exit(C, op);
-       return OPERATOR_CANCELLED;
 }
 
 static int knifetool_invoke(bContext *C, wmOperator *op, const wmEvent *event)
@@ -3045,6 +2729,15 @@ static int knifetool_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 
        KnifeTool_OpData *kcd;
 
+       if (only_select) {
+               Object *obedit = CTX_data_edit_object(C);
+               BMEditMesh *em = BKE_editmesh_from_object(obedit);
+               if (em->bm->totfacesel == 0) {
+                       BKE_report(op->reports, RPT_ERROR, "Selected faces required");
+                       return OPERATOR_CANCELLED;
+               }
+       }
+
        view3d_operator_needs_opengl(C);
 
        /* alloc new customdata */
@@ -3053,7 +2746,7 @@ static int knifetool_invoke(bContext *C, wmOperator *op, const wmEvent *event)
        knifetool_init(C, kcd, only_select, cut_through, true);
 
        /* add a modal handler for this operator - handles loop selection */
-       WM_cursor_modal(CTX_wm_window(C), BC_KNIFECURSOR);
+       WM_cursor_modal_set(CTX_wm_window(C), BC_KNIFECURSOR);
        WM_event_add_modal_handler(C, op);
 
        knifetool_update_mval_i(kcd, event->mval);
@@ -3073,7 +2766,8 @@ enum {
        KNF_MODEL_IGNORE_SNAP_OFF,
        KNF_MODAL_ADD_CUT,
        KNF_MODAL_ANGLE_SNAP_TOGGLE,
-       KNF_MODAL_CUT_THROUGH_TOGGLE
+       KNF_MODAL_CUT_THROUGH_TOGGLE,
+       KNF_MODAL_PANNING
 };
 
 wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf)
@@ -3089,6 +2783,7 @@ wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf)
                {KNF_MODAL_CUT_THROUGH_TOGGLE, "CUT_THROUGH_TOGGLE", 0, "Toggle Cut Through", ""},
                {KNF_MODAL_NEW_CUT, "NEW_CUT", 0, "End Current Cut", ""},
                {KNF_MODAL_ADD_CUT, "ADD_CUT", 0, "Add Cut", ""},
+               {KNF_MODAL_PANNING, "PANNING", 0, "Panning", ""},
                {0, NULL, 0, NULL, NULL}
        };
 
@@ -3102,6 +2797,7 @@ 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, RIGHTMOUSE, KM_PRESS, KM_ANY, 0, KNF_MODAL_CANCEL);
        WM_modalkeymap_add_item(keymap, RETKEY, KM_PRESS, KM_ANY, 0, KNF_MODAL_CONFIRM);
@@ -3215,10 +2911,6 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
 
                                if (kcd->mode == MODE_DRAGGING) {
                                        knife_add_cut(kcd);
-                                       if (!kcd->extend) {
-                                               knife_finish_cut(kcd);
-                                               kcd->mode = MODE_IDLE;
-                                       }
                                }
                                else if (kcd->mode != MODE_PANNING) {
                                        knife_start_cut(kcd);
@@ -3227,18 +2919,12 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
 
                                ED_region_tag_redraw(kcd->ar);
                                break;
-               }
-       }
-       else { /* non-modal-mapped events */
-               switch (event->type) {
-                       case WHEELUPMOUSE:
-                       case WHEELDOWNMOUSE:
-                               return OPERATOR_PASS_THROUGH;
-                       case MIDDLEMOUSE:
+                       case KNF_MODAL_PANNING:
                                if (event->val != KM_RELEASE) {
-                                       if (kcd->mode != MODE_PANNING)
+                                       if (kcd->mode != MODE_PANNING) {
                                                kcd->prevmode = kcd->mode;
-                                       kcd->mode = MODE_PANNING;
+                                               kcd->mode = MODE_PANNING;
+                                       }
                                }
                                else {
                                        kcd->mode = kcd->prevmode;
@@ -3246,7 +2932,16 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
 
                                ED_region_tag_redraw(kcd->ar);
                                return OPERATOR_PASS_THROUGH;
-
+               }
+       }
+       else { /* non-modal-mapped events */
+               switch (event->type) {
+                       case MOUSEPAN:
+                       case MOUSEZOOM:
+                       case MOUSEROTATE:
+                       case WHEELUPMOUSE:
+                       case WHEELDOWNMOUSE:
+                               return OPERATOR_PASS_THROUGH;
                        case MOUSEMOVE: /* mouse moved somewhere to select another loop */
                                if (kcd->mode != MODE_PANNING) {
                                        knifetool_update_mval_i(kcd, event->mval);
@@ -3299,17 +2994,16 @@ void MESH_OT_knife_tool(wmOperatorType *ot)
  */
 static void edvm_mesh_knife_face_point(BMFace *f, float r_cent[3])
 {
-       int tottri = f->len - 2;
-       BMLoop **loops     = BLI_array_alloca(loops, f->len);
-       int    (*index)[3] = BLI_array_alloca(index, tottri);
+       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;
 
-       float const *best_co[3] = {NULL};
-       float  best_area  = -1.0f;
+       const float *best_co[3] = {NULL};
+       float best_area  = -1.0f;
        bool ok = false;
 
-       tottri = BM_face_calc_tessellation(f, loops, index);
-       BLI_assert(tottri <= f->len - 2);
+       BM_face_calc_tessellation(f, loops, index);
 
        for (j = 0; j < tottri; j++) {
                const float *p1 = loops[index[j][0]]->v->co;
@@ -3354,7 +3048,7 @@ static bool edbm_mesh_knife_face_isect(ARegion *ar, LinkNode *polys, BMFace *f,
                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);
+                       isect += (int)isect_point_poly_v2(cent_ss, mval_fl, mval_tot - 1, false);
                        p = p->next;
                }
 
@@ -3369,7 +3063,7 @@ static bool edbm_mesh_knife_face_isect(ARegion *ar, LinkNode *polys, BMFace *f,
 /**
  * \param use_tag  When set, tag all faces inside the polylines.
  */
-void EDBM_mesh_knife(bContext *C, LinkNode *polys, bool use_tag)
+void EDBM_mesh_knife(bContext *C, LinkNode *polys, bool use_tag, bool cut_through)
 {
        KnifeTool_OpData *kcd;
 
@@ -3378,7 +3072,6 @@ void EDBM_mesh_knife(bContext *C, LinkNode *polys, bool use_tag)
        /* init */
        {
                const bool only_select = false;
-               const bool cut_through = false;
                const bool is_interactive = false;  /* can enable for testing */
 
                kcd = MEM_callocN(sizeof(KnifeTool_OpData), __func__);