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:
-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);