Welcome to mirror list, hosted at ThFree Co, Russian Federation.

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