diff options
-rw-r--r-- | release/scripts/presets/keyconfig/keymap_data/blender_default.py | 10 | ||||
-rw-r--r-- | release/scripts/startup/bl_ui/space_toolsystem_toolbar.py | 18 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_paint.h | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/paint.c | 2 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt.c | 356 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_intern.h | 18 |
6 files changed, 401 insertions, 4 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 30ef5c5e561..6d4c3616ca4 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -5718,6 +5718,15 @@ def km_3d_view_tool_sculpt_lasso_mask(params): ]}, ) +def km_3d_view_tool_sculpt_mesh_filter(params): + return ( + "3D View Tool: Sculpt, Mesh Filter", + {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, + {"items": [ + ("sculpt.mesh_filter", {"type": params.tool_tweak, "value": 'ANY'}, + None) + ]}, + ) def km_3d_view_tool_paint_weight_sample_weight(params): return ( @@ -6159,6 +6168,7 @@ def generate_keymaps(params=None): km_3d_view_tool_sculpt_box_hide(params), km_3d_view_tool_sculpt_box_mask(params), km_3d_view_tool_sculpt_lasso_mask(params), + km_3d_view_tool_sculpt_mesh_filter(params), km_3d_view_tool_paint_weight_sample_weight(params), km_3d_view_tool_paint_weight_sample_vertex_group(params), km_3d_view_tool_paint_weight_gradient(params), diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 1403c1fb532..f868c945512 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -985,6 +985,22 @@ class _defs_sculpt: keymap=(), ) + @ToolDef.from_fn + def mesh_filter(): + def draw_settings(context, layout, tool): + props = tool.operator_properties("sculpt.mesh_filter") + layout.prop(props, "type", expand=False) + layout.prop(props, "strength") + layout.prop(props, "deform_axis") + + return dict( + idname="builtin.mesh_filter", + label="Mesh Filter", + icon="ops.sculpt.mesh_filter", + widget=None, + keymap= (), + draw_settings=draw_settings, + ) class _defs_vertex_paint: @@ -1967,6 +1983,8 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): ), _defs_sculpt.hide_border, None, + _defs_sculpt.mesh_filter, + None, *_tools_annotate, ], 'PAINT_TEXTURE': [ diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index ed02a34196f..ef798479b23 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -256,6 +256,7 @@ typedef struct SculptSession { float (*layer_co)[3]; /* Copy of the mesh vertices' locations */ struct StrokeCache *cache; + struct FilterCache *filter_cache; /* Cursor data and active vertex for tools */ int active_vertex_index; diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index 86b3e347513..b386a0d4483 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -1268,7 +1268,7 @@ void BKE_sculpt_update_object_before_eval(Object *ob) SculptSession *ss = ob->sculpt; if (ss && ss->building_vp_handle == false) { - if (!ss->cache) { + if (!ss->cache && !ss->filter_cache) { /* We free pbvh on changes, except in the middle of drawing a stroke * since it can't deal with changing PVBH node organization, we hope * topology does not change in the meantime .. weak. */ diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index dce8ea105e4..9ffeeb5a232 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -27,6 +27,7 @@ #include "BLI_math.h" #include "BLI_blenlib.h" #include "BLI_dial_2d.h" +#include "BLI_hash.h" #include "BLI_task.h" #include "BLI_utildefines.h" #include "BLI_ghash.h" @@ -108,6 +109,13 @@ static float *sculpt_vertex_co_get(SculptSession *ss, int index) } } +static void sculpt_vertex_random_access_init(SculptSession *ss) +{ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BM_mesh_elem_index_ensure(ss->bm, BM_VERT); + } +} + #if 0 /* UNUSED */ static int sculpt_active_vertex_get(SculptSession *ss) @@ -134,6 +142,7 @@ static int sculpt_vertex_count_get(SculptSession *ss) } } + static void sculpt_vertex_normal_get(SculptSession *ss, int index, float no[3]) { switch (BKE_pbvh_type(ss->pbvh)) { @@ -4766,12 +4775,12 @@ static void sculpt_flush_stroke_deform_task_cb(void *__restrict userdata, } /* flush displacement from deformed PBVH to original layer */ -static void sculpt_flush_stroke_deform(Sculpt *sd, Object *ob) +static void sculpt_flush_stroke_deform(Sculpt *sd, Object *ob, bool is_proxy_used) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); - if (sculpt_tool_is_proxy_used(brush->sculpt_tool)) { + if (is_proxy_used) { /* this brushes aren't using proxies, so sculpt_combine_proxies() wouldn't * propagate needed deformation to original base */ @@ -6109,7 +6118,7 @@ static void sculpt_stroke_update_step(bContext *C, * sculpt_flush_update_step(). */ if (ss->modifiers_active) { - sculpt_flush_stroke_deform(sd, ob); + sculpt_flush_stroke_deform(sd, ob, sculpt_tool_is_proxy_used(brush->sculpt_tool)); } else if (ss->kb) { sculpt_update_keyblock(ob); @@ -7256,6 +7265,346 @@ static void SCULPT_OT_set_detail_size(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +static void filter_cache_init_task_cb(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + PBVHNode *node = data->nodes[i]; + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) + { + if (!vd.mask || (vd.mask && *vd.mask < 1.0f)) { + data->node_mask[i] = 1; + } + } + BKE_pbvh_vertex_iter_end; + + if (data->node_mask[i] == 1) { + sculpt_undo_push_node(data->ob, node, SCULPT_UNDO_COORDS); + } +} + +static void sculpt_filter_cache_init(Object *ob, Sculpt *sd) +{ + SculptSession *ss = ob->sculpt; + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + + ss->filter_cache = MEM_callocN(sizeof(FilterCache), "filter cache"); + + ss->filter_cache->random_seed = rand(); + + SculptSearchSphereData search_data = { + .original = true, + }; + BKE_pbvh_search_gather(pbvh, NULL, &search_data, &nodes, &totnode); + + int *node_mask = MEM_callocN((unsigned int)totnode * sizeof(int), "node mask"); + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_normals_update(nodes[i]); + } + BKE_pbvh_update_normals(ss->pbvh, NULL); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = nodes, + .node_mask = node_mask, + }; + + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT); + BLI_task_parallel_range(0, totnode, &data, filter_cache_init_task_cb, &settings); + + int tot_active_nodes = 0; + int active_node_index = 0; + PBVHNode **active_nodes; + + /* Count number of PBVH nodes that are not fully masked */ + for (int i = 0; i < totnode; i++) { + if (node_mask[i] == 1) { + tot_active_nodes++; + } + } + + /* Create the final list of nodes that is going to be processed in the filter */ + active_nodes = MEM_callocN(tot_active_nodes * sizeof(PBVHNode *), "active nodes"); + + for (int i = 0; i < totnode; i++) { + if (node_mask[i] == 1) { + active_nodes[active_node_index] = nodes[i]; + active_node_index++; + } + } + + ss->filter_cache->nodes = active_nodes; + ss->filter_cache->totnode = tot_active_nodes; + + if (nodes) { + MEM_freeN(nodes); + } + if (node_mask) { + MEM_freeN(node_mask); + } +} + +static void sculpt_filter_cache_free(SculptSession *ss) +{ + if (ss->filter_cache->nodes) { + MEM_freeN(ss->filter_cache->nodes); + } + if (ss->filter_cache->mask_update_it) { + MEM_freeN(ss->filter_cache->mask_update_it); + } + MEM_freeN(ss->filter_cache); + ss->filter_cache = NULL; +} + +typedef enum eSculptMeshFilterTypes { + MESH_FILTER_SMOOTH = 0, + MESH_FILTER_SCALE = 1, + MESH_FILTER_INFLATE = 2, + MESH_FILTER_SPHERE = 3, + MESH_FILTER_RANDOM = 4, +} eSculptMeshFilterTypes; + +static EnumPropertyItem prop_mesh_filter_types[] = { + {MESH_FILTER_SMOOTH, "SMOOTH", 0, "Smooth", "Smooth mesh"}, + {MESH_FILTER_SCALE, "SCALE", 0, "Scale", "Scale mesh"}, + {MESH_FILTER_INFLATE, "INFLATE", 0, "Inflate", "Inflate mesh"}, + {MESH_FILTER_SPHERE, "SPHERE", 0, "Sphere", "Morph into sphere"}, + {MESH_FILTER_RANDOM, "RANDOM", 0, "Random", "Randomize vertex positions"}, + {0, NULL, 0, NULL, NULL}, +}; + +typedef enum eMeshFilterDeformAxis { + MESH_FILTER_DEFORM_X = 1 << 0, + MESH_FILTER_DEFORM_Y = 1 << 1, + MESH_FILTER_DEFORM_Z = 1 << 2, +} eMeshFilterDeformAxis; + +static EnumPropertyItem prop_mesh_filter_deform_axis_items[] = { + {MESH_FILTER_DEFORM_X, "X", 0, "X", "Deform in the X axis"}, + {MESH_FILTER_DEFORM_Y, "Y", 0, "Y", "Deform in the Y axis"}, + {MESH_FILTER_DEFORM_Z, "Z", 0, "Z", "Deform in the Z axis"}, + {0, NULL, 0, NULL, NULL}, +}; + +static void mesh_filter_task_cb(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + PBVHNode *node = data->nodes[i]; + + const int filter_type = data->filter_type; + + SculptOrigVertData orig_data; + sculpt_orig_vert_data_init(&orig_data, data->ob, data->nodes[i]); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) + { + sculpt_orig_vert_data_update(&orig_data, &vd); + float orig_co[3], val[3], avg[3], normal[3], disp[3], disp2[3], transform[3][3], final_pos[3]; + float fade = vd.mask ? *vd.mask : 0.0f; + fade = 1 - fade; + fade *= data->filter_strength; + copy_v3_v3(orig_co, orig_data.co); + switch (filter_type) { + case MESH_FILTER_SMOOTH: + CLAMP(fade, -1.0f, 1.0f); + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + neighbor_average(ss, avg, vd.index); + } + else if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + bmesh_neighbor_average(avg, vd.bm_vert); + } + sub_v3_v3v3(val, avg, orig_co); + madd_v3_v3v3fl(val, orig_co, val, fade); + sub_v3_v3v3(disp, val, orig_co); + break; + case MESH_FILTER_INFLATE: + normal_short_to_float_v3(normal, orig_data.no); + mul_v3_v3fl(disp, normal, fade); + break; + case MESH_FILTER_SCALE: + unit_m3(transform); + scale_m3_fl(transform, 1 + fade); + copy_v3_v3(val, orig_co); + mul_m3_v3(transform, val); + sub_v3_v3v3(disp, val, orig_co); + break; + case MESH_FILTER_SPHERE: + normalize_v3_v3(disp, orig_co); + if (fade > 0) { + mul_v3_v3fl(disp, disp, fade); + } + else { + mul_v3_v3fl(disp, disp, -fade); + } + + unit_m3(transform); + if (fade > 0) { + scale_m3_fl(transform, 1 - fade); + } + else { + scale_m3_fl(transform, 1 + fade); + } + copy_v3_v3(val, orig_co); + mul_m3_v3(transform, val); + sub_v3_v3v3(disp2, val, orig_co); + + mid_v3_v3v3(disp, disp, disp2); + break; + case MESH_FILTER_RANDOM: + normal_short_to_float_v3(normal, orig_data.no); + mul_v3_fl(normal, BLI_hash_int_01(vd.index ^ ss->filter_cache->random_seed) - 0.5f); + mul_v3_v3fl(disp, normal, fade); + break; + } + + for (int it = 0; it < 3; it++) { + if (!ss->filter_cache->enabled_axis[it]) { + disp[it] = 0.0f; + } + } + + add_v3_v3v3(final_pos, orig_co, disp); + copy_v3_v3(vd.co, final_pos); + if (vd.mvert) + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + BKE_pbvh_vertex_iter_end; + + BKE_pbvh_node_mark_redraw(node); + BKE_pbvh_node_mark_normals_update(node); +} + +static int sculpt_mesh_filter_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + int filter_type = RNA_enum_get(op->ptr, "type"); + float filter_strength = RNA_float_get(op->ptr, "strength"); + + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + sculpt_filter_cache_free(ss); + sculpt_undo_push_end(); + sculpt_flush_update_done(C, ob); + return OPERATOR_FINISHED; + } + + if (event->type != MOUSEMOVE) { + return OPERATOR_RUNNING_MODAL; + } + + float len = event->prevclickx - event->mval[0]; + filter_strength = filter_strength * -len * 0.001f * UI_DPI_FAC; + + sculpt_vertex_random_access_init(ss); + + bool needs_pmap = (filter_type == MESH_FILTER_SMOOTH); + BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_pmap, false); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + .filter_type = filter_type, + .filter_strength = filter_strength, + }; + + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && + ss->filter_cache->totnode > SCULPT_THREADED_LIMIT); + BLI_task_parallel_range(0, ss->filter_cache->totnode, &data, mesh_filter_task_cb, &settings); + + if (ss->modifiers_active || ss->kb) { + sculpt_flush_stroke_deform(sd, ob, true); + } + + sculpt_flush_update_step(C); + + return OPERATOR_RUNNING_MODAL; +} + +static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + Object *ob = CTX_data_active_object(C); + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + int filter_type = RNA_enum_get(op->ptr, "type"); + SculptSession *ss = ob->sculpt; + PBVH *pbvh = ob->sculpt->pbvh; + + int deform_axis = RNA_enum_get(op->ptr, "deform_axis"); + if (deform_axis == 0) { + return OPERATOR_CANCELLED; + } + + sculpt_vertex_random_access_init(ss); + + bool needs_pmap = (filter_type == MESH_FILTER_SMOOTH); + BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_pmap, false); + + if (BKE_pbvh_type(pbvh) == PBVH_FACES && needs_pmap && !ob->sculpt->pmap) { + return OPERATOR_CANCELLED; + } + + sculpt_undo_push_begin("Mesh filter"); + + sculpt_filter_cache_init(ob, sd); + + ss->filter_cache->enabled_axis[0] = deform_axis & MESH_FILTER_DEFORM_X; + ss->filter_cache->enabled_axis[1] = deform_axis & MESH_FILTER_DEFORM_Y; + ss->filter_cache->enabled_axis[2] = deform_axis & MESH_FILTER_DEFORM_Z; + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static void SCULPT_OT_mesh_filter(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Filter mesh"; + ot->idname = "SCULPT_OT_mesh_filter"; + ot->description = "Applies a filter to modify the current mesh"; + + /* api callbacks */ + ot->invoke = sculpt_mesh_filter_invoke; + ot->modal = sculpt_mesh_filter_modal; + ot->poll = sculpt_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* rna */ + RNA_def_enum(ot->srna, + "type", + prop_mesh_filter_types, + MESH_FILTER_INFLATE, + "Filter type", + "Operation that is going to be applied to the mesh"); + RNA_def_float( + ot->srna, "strength", 1.0f, -10.0f, 10.0f, "Strength", "Filter Strength", -10.0f, 10.0f); + RNA_def_enum_flag(ot->srna, + "deform_axis", + prop_mesh_filter_deform_axis_items, + MESH_FILTER_DEFORM_X | MESH_FILTER_DEFORM_Y | MESH_FILTER_DEFORM_Z, + "Deform axis", + "Apply the deformation in the selected axis"); +} + void ED_operatortypes_sculpt(void) { WM_operatortype_append(SCULPT_OT_brush_stroke); @@ -7267,4 +7616,5 @@ void ED_operatortypes_sculpt(void) WM_operatortype_append(SCULPT_OT_detail_flood_fill); WM_operatortype_append(SCULPT_OT_sample_detail_size); WM_operatortype_append(SCULPT_OT_set_detail_size); + WM_operatortype_append(SCULPT_OT_mesh_filter); } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 9f1cb7a53a4..a248476fd49 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -175,6 +175,10 @@ typedef struct SculptThreadedTaskData { int *count; bool any_vertex_sampled; + int filter_type; + float filter_strength; + int *node_mask; + ThreadMutex mutex; } SculptThreadedTaskData; @@ -341,6 +345,20 @@ typedef struct StrokeCache { } StrokeCache; +typedef struct FilterCache { + bool enabled_axis[3]; + int random_seed; + + /* unmasked nodes */ + PBVHNode **nodes; + int totnode; + + /* mask expand iteration caches */ + int mask_update_current_it; + int mask_update_last_it; + int *mask_update_it; +} FilterCache; + void sculpt_cache_calc_brushdata_symm(StrokeCache *cache, const char symm, const char axis, |