diff options
author | Pablo Dobarro <pablodp606@gmail.com> | 2020-04-03 22:46:08 +0300 |
---|---|---|
committer | Pablo Dobarro <pablodp606@gmail.com> | 2020-04-03 22:46:08 +0300 |
commit | 17931f3b5125528e3d763ae8a80eae5e4730dcc3 (patch) | |
tree | f0917bab236dd01a2c0929492bfa0b013f956e80 /source/blender | |
parent | f2f30db98dacf2821fce3389952798c597bffe11 (diff) |
Cleanup: Move Mask Filter and Mask Expand to their own files
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/editors/sculpt_paint/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt.c | 893 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_automasking.c | 10 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_filter_mask.c | 501 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_filter_mesh.c | 22 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_intern.h | 12 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_mask_expand.c | 526 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_smooth.c | 29 |
8 files changed, 1071 insertions, 924 deletions
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 81dafd5ded4..f9858804394 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -62,7 +62,9 @@ set(SRC sculpt_automasking.c sculpt_cloth.c sculpt_face_set.c + sculpt_filter_mask.c sculpt_filter_mesh.c + sculpt_mask_expand.c sculpt_multiplane_scrape.c sculpt_pose.c sculpt_smooth.c diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index a53c9aed36d..5bd0f3f2f48 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -153,7 +153,7 @@ const float *SCULPT_vertex_co_get(SculptSession *ss, int index) return NULL; } -static void SCULPT_vertex_normal_get(SculptSession *ss, int index, float no[3]) +void SCULPT_vertex_normal_get(SculptSession *ss, int index, float no[3]) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: @@ -209,7 +209,7 @@ const float *SCULPT_active_vertex_co_get(SculptSession *ss) return SCULPT_vertex_co_get(ss, SCULPT_active_vertex_get(ss)); } -static void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]) +void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]) { SCULPT_vertex_normal_get(ss, SCULPT_active_vertex_get(ss), normal); } @@ -8463,895 +8463,6 @@ static void SCULPT_OT_set_detail_size(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -typedef enum eSculptMaskFilterTypes { - MASK_FILTER_SMOOTH = 0, - MASK_FILTER_SHARPEN = 1, - MASK_FILTER_GROW = 2, - MASK_FILTER_SHRINK = 3, - MASK_FILTER_CONTRAST_INCREASE = 5, - MASK_FILTER_CONTRAST_DECREASE = 6, -} eSculptMaskFilterTypes; - -static EnumPropertyItem prop_mask_filter_types[] = { - {MASK_FILTER_SMOOTH, "SMOOTH", 0, "Smooth Mask", "Smooth mask"}, - {MASK_FILTER_SHARPEN, "SHARPEN", 0, "Sharpen Mask", "Sharpen mask"}, - {MASK_FILTER_GROW, "GROW", 0, "Grow Mask", "Grow mask"}, - {MASK_FILTER_SHRINK, "SHRINK", 0, "Shrink Mask", "Shrink mask"}, - {MASK_FILTER_CONTRAST_INCREASE, - "CONTRAST_INCREASE", - 0, - "Increase contrast", - "Increase the contrast of the paint mask"}, - {MASK_FILTER_CONTRAST_DECREASE, - "CONTRAST_DECREASE", - 0, - "Decrease contrast", - "Decrease the contrast of the paint mask"}, - {0, NULL, 0, NULL, NULL}, -}; - -static void mask_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]; - bool update = false; - - const int mode = data->filter_type; - float contrast = 0.0f; - - PBVHVertexIter vd; - - if (mode == MASK_FILTER_CONTRAST_INCREASE) { - contrast = 0.1f; - } - - if (mode == MASK_FILTER_CONTRAST_DECREASE) { - contrast = -0.1f; - } - - BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) - { - float delta, gain, offset, max, min; - float prev_val = *vd.mask; - SculptVertexNeighborIter ni; - switch (mode) { - case MASK_FILTER_SMOOTH: - case MASK_FILTER_SHARPEN: { - float val = SCULPT_neighbor_mask_average(ss, vd.index); - - val -= *vd.mask; - - if (mode == MASK_FILTER_SMOOTH) { - *vd.mask += val; - } - else if (mode == MASK_FILTER_SHARPEN) { - if (*vd.mask > 0.5f) { - *vd.mask += 0.05f; - } - else { - *vd.mask -= 0.05f; - } - *vd.mask += val / 2.0f; - } - break; - } - case MASK_FILTER_GROW: - max = 0.0f; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { - float vmask_f = data->prev_mask[ni.index]; - if (vmask_f > max) { - max = vmask_f; - } - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - *vd.mask = max; - break; - case MASK_FILTER_SHRINK: - min = 1.0f; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { - float vmask_f = data->prev_mask[ni.index]; - if (vmask_f < min) { - min = vmask_f; - } - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - *vd.mask = min; - break; - case MASK_FILTER_CONTRAST_INCREASE: - case MASK_FILTER_CONTRAST_DECREASE: - delta = contrast / 2.0f; - gain = 1.0f - delta * 2.0f; - if (contrast > 0) { - gain = 1.0f / ((gain != 0.0f) ? gain : FLT_EPSILON); - offset = gain * (-delta); - } - else { - delta *= -1.0f; - offset = gain * (delta); - } - *vd.mask = gain * (*vd.mask) + offset; - break; - } - CLAMP(*vd.mask, 0.0f, 1.0f); - if (*vd.mask != prev_val) { - update = true; - } - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; - - if (update) { - BKE_pbvh_node_mark_update_mask(node); - } -} - -static int sculpt_mask_filter_exec(bContext *C, wmOperator *op) -{ - ARegion *region = CTX_wm_region(C); - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - PBVH *pbvh = ob->sculpt->pbvh; - PBVHNode **nodes; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - int totnode; - int filter_type = RNA_enum_get(op->ptr, "filter_type"); - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); - - SCULPT_vertex_random_access_init(ss); - - if (!ob->sculpt->pmap) { - return OPERATOR_CANCELLED; - } - - int num_verts = SCULPT_vertex_count_get(ss); - - BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - SCULPT_undo_push_begin("Mask filter"); - - for (int i = 0; i < totnode; i++) { - SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK); - } - - float *prev_mask = NULL; - int iterations = RNA_int_get(op->ptr, "iterations"); - - /* Auto iteration count calculates the number of iteration based on the vertices of the mesh to - * avoid adding an unnecessary amount of undo steps when using the operator from a shortcut. - * One iteration per 50000 vertices in the mesh should be fine in most cases. - * Maybe we want this to be configurable. */ - if (RNA_boolean_get(op->ptr, "auto_iteration_count")) { - iterations = (int)(num_verts / 50000.0f) + 1; - } - - for (int i = 0; i < iterations; i++) { - if (ELEM(filter_type, MASK_FILTER_GROW, MASK_FILTER_SHRINK)) { - prev_mask = MEM_mallocN(num_verts * sizeof(float), "prevmask"); - for (int j = 0; j < num_verts; j++) { - prev_mask[j] = SCULPT_vertex_mask_get(ss, j); - } - } - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .nodes = nodes, - .filter_type = filter_type, - .prev_mask = prev_mask, - }; - - PBVHParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode); - BKE_pbvh_parallel_range(0, totnode, &data, mask_filter_task_cb, &settings); - - if (ELEM(filter_type, MASK_FILTER_GROW, MASK_FILTER_SHRINK)) { - MEM_freeN(prev_mask); - } - } - - MEM_SAFE_FREE(nodes); - - SCULPT_undo_push_end(); - - ED_region_tag_redraw(region); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_mask_filter(struct wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Mask Filter"; - ot->idname = "SCULPT_OT_mask_filter"; - ot->description = "Applies a filter to modify the current mask"; - - /* API callbacks. */ - ot->exec = sculpt_mask_filter_exec; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER; - - /* RNA. */ - RNA_def_enum(ot->srna, - "filter_type", - prop_mask_filter_types, - MASK_FILTER_SMOOTH, - "Type", - "Filter that is going to be applied to the mask"); - RNA_def_int(ot->srna, - "iterations", - 1, - 1, - 100, - "Iterations", - "Number of times that the filter is going to be applied", - 1, - 100); - RNA_def_boolean( - ot->srna, - "auto_iteration_count", - false, - "Auto Iteration Count", - "Use a automatic number of iterations based on the number of vertices of the sculpt"); -} - -static float neighbor_dirty_mask(SculptSession *ss, PBVHVertexIter *vd) -{ - int total = 0; - float avg[3]; - zero_v3(avg); - - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) { - float normalized[3]; - sub_v3_v3v3(normalized, SCULPT_vertex_co_get(ss, ni.index), vd->co); - normalize_v3(normalized); - add_v3_v3(avg, normalized); - total++; - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - - if (total > 0) { - mul_v3_fl(avg, 1.0f / total); - float normal[3]; - if (vd->no) { - normal_short_to_float_v3(normal, vd->no); - } - else { - copy_v3_v3(normal, vd->fno); - } - float dot = dot_v3v3(avg, normal); - float angle = max_ff(saacosf(dot), 0.0f); - return angle; - } - return 0.0f; -} - -typedef struct DirtyMaskRangeData { - float min, max; -} DirtyMaskRangeData; - -static void dirty_mask_compute_range_task_cb(void *__restrict userdata, - const int i, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - PBVHNode *node = data->nodes[i]; - DirtyMaskRangeData *range = tls->userdata_chunk; - PBVHVertexIter vd; - - BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) - { - float dirty_mask = neighbor_dirty_mask(ss, &vd); - range->min = min_ff(dirty_mask, range->min); - range->max = max_ff(dirty_mask, range->max); - } - BKE_pbvh_vertex_iter_end; -} - -static void dirty_mask_compute_range_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - DirtyMaskRangeData *join = chunk_join; - DirtyMaskRangeData *range = chunk; - join->min = min_ff(range->min, join->min); - join->max = max_ff(range->max, join->max); -} - -static void dirty_mask_apply_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; - - const bool dirty_only = data->dirty_mask_dirty_only; - const float min = data->dirty_mask_min; - const float max = data->dirty_mask_max; - - float range = max - min; - if (range < 0.0001f) { - range = 0.0f; - } - else { - range = 1.0f / range; - } - - BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) - { - float dirty_mask = neighbor_dirty_mask(ss, &vd); - float mask = *vd.mask + (1.0f - ((dirty_mask - min) * range)); - if (dirty_only) { - mask = fminf(mask, 0.5f) * 2.0f; - } - *vd.mask = CLAMPIS(mask, 0.0f, 1.0f); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; - BKE_pbvh_node_mark_update_mask(node); -} - -static int sculpt_dirty_mask_exec(bContext *C, wmOperator *op) -{ - ARegion *region = CTX_wm_region(C); - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - PBVH *pbvh = ob->sculpt->pbvh; - PBVHNode **nodes; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - int totnode; - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); - - SCULPT_vertex_random_access_init(ss); - - if (!ob->sculpt->pmap) { - return OPERATOR_CANCELLED; - } - - BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - SCULPT_undo_push_begin("Dirty Mask"); - - for (int i = 0; i < totnode; i++) { - SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK); - } - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .nodes = nodes, - .dirty_mask_dirty_only = RNA_boolean_get(op->ptr, "dirty_only"), - }; - DirtyMaskRangeData range = { - .min = FLT_MAX, - .max = -FLT_MAX, - }; - - PBVHParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode); - - settings.func_reduce = dirty_mask_compute_range_reduce; - settings.userdata_chunk = ⦥ - settings.userdata_chunk_size = sizeof(DirtyMaskRangeData); - - BKE_pbvh_parallel_range(0, totnode, &data, dirty_mask_compute_range_task_cb, &settings); - data.dirty_mask_min = range.min; - data.dirty_mask_max = range.max; - BKE_pbvh_parallel_range(0, totnode, &data, dirty_mask_apply_task_cb, &settings); - - MEM_SAFE_FREE(nodes); - - BKE_pbvh_update_vertex_data(pbvh, PBVH_UpdateMask); - - SCULPT_undo_push_end(); - - ED_region_tag_redraw(region); - - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_dirty_mask(struct wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Dirty Mask"; - ot->idname = "SCULPT_OT_dirty_mask"; - ot->description = "Generates a mask based on the geometry cavity and pointiness"; - - /* API callbacks. */ - ot->exec = sculpt_dirty_mask_exec; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER; - - /* RNA. */ - RNA_def_boolean( - ot->srna, "dirty_only", false, "Dirty Only", "Don't calculate cleans for convex areas"); -} - -static void sculpt_mask_expand_cancel(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set"); - - MEM_freeN(op->customdata); - - for (int n = 0; n < ss->filter_cache->totnode; n++) { - PBVHNode *node = ss->filter_cache->nodes[n]; - if (create_face_set) { - for (int i = 0; i < ss->totfaces; i++) { - ss->face_sets[i] = ss->filter_cache->prev_face_set[i]; - } - } - else { - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) - { - *vd.mask = ss->filter_cache->prev_mask[vd.index]; - } - BKE_pbvh_vertex_iter_end; - } - - BKE_pbvh_node_mark_redraw(node); - } - - if (!create_face_set) { - SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); - } - SCULPT_filter_cache_free(ss); - SCULPT_undo_push_end(); - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); - ED_workspace_status_text(C, NULL); -} - -static void sculpt_expand_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; - int update_it = data->mask_expand_update_it; - - BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_ALL) - { - int vi = vd.index; - float final_mask = *vd.mask; - if (data->mask_expand_use_normals) { - if (ss->filter_cache->normal_factor[SCULPT_active_vertex_get(ss)] < - ss->filter_cache->normal_factor[vd.index]) { - final_mask = 1.0f; - } - else { - final_mask = 0.0f; - } - } - else { - if (ss->filter_cache->mask_update_it[vi] <= update_it && - ss->filter_cache->mask_update_it[vi] != 0) { - final_mask = 1.0f; - } - else { - final_mask = 0.0f; - } - } - - if (data->mask_expand_create_face_set) { - if (final_mask == 1.0f) { - SCULPT_vertex_face_set_set(ss, vd.index, ss->filter_cache->new_face_set); - } - BKE_pbvh_node_mark_redraw(node); - } - else { - - if (data->mask_expand_keep_prev_mask) { - final_mask = MAX2(ss->filter_cache->prev_mask[vd.index], final_mask); - } - - if (data->mask_expand_invert_mask) { - final_mask = 1.0f - final_mask; - } - - if (*vd.mask != final_mask) { - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - *vd.mask = final_mask; - BKE_pbvh_node_mark_update_mask(node); - } - } - } - BKE_pbvh_vertex_iter_end; -} - -static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - ARegion *region = CTX_wm_region(C); - float prevclick_f[2]; - copy_v2_v2(prevclick_f, op->customdata); - int prevclick[2] = {(int)prevclick_f[0], (int)prevclick_f[1]}; - int len = (int)len_v2v2_int(prevclick, event->mval); - len = abs(len); - int mask_speed = RNA_int_get(op->ptr, "mask_speed"); - int mask_expand_update_it = len / mask_speed; - mask_expand_update_it = mask_expand_update_it + 1; - - const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set"); - - if (RNA_boolean_get(op->ptr, "use_cursor")) { - SculptCursorGeometryInfo sgi; - float mouse[2]; - mouse[0] = event->mval[0]; - mouse[1] = event->mval[1]; - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); - mask_expand_update_it = ss->filter_cache->mask_update_it[(int)SCULPT_active_vertex_get(ss)]; - } - - if ((event->type == EVT_ESCKEY && event->val == KM_PRESS) || - (event->type == RIGHTMOUSE && event->val == KM_PRESS)) { - /* Returning OPERATOR_CANCELLED will leak memory due to not finishing - * undo. Better solution could be to make paint_mesh_restore_co work - * for this case. */ - sculpt_mask_expand_cancel(C, op); - return OPERATOR_FINISHED; - } - - if ((event->type == LEFTMOUSE && event->val == KM_RELEASE) || - (event->type == EVT_RETKEY && event->val == KM_PRESS) || - (event->type == EVT_PADENTER && event->val == KM_PRESS)) { - - /* Smooth iterations. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .nodes = ss->filter_cache->nodes, - .filter_type = MASK_FILTER_SMOOTH, - }; - - int smooth_iterations = RNA_int_get(op->ptr, "smooth_iterations"); - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false); - for (int i = 0; i < smooth_iterations; i++) { - PBVHParallelSettings settings; - BKE_pbvh_parallel_range_settings( - &settings, (sd->flags & SCULPT_USE_OPENMP), ss->filter_cache->totnode); - BKE_pbvh_parallel_range(0, ss->filter_cache->totnode, &data, mask_filter_task_cb, &settings); - } - - /* Pivot position. */ - if (RNA_boolean_get(op->ptr, "update_pivot")) { - const char symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; - const float threshold = 0.2f; - float avg[3]; - int total = 0; - zero_v3(avg); - - for (int n = 0; n < ss->filter_cache->totnode; n++) { - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin(ss->pbvh, ss->filter_cache->nodes[n], vd, PBVH_ITER_UNIQUE) - { - const float mask = (vd.mask) ? *vd.mask : 0.0f; - if (mask < (0.5f + threshold) && mask > (0.5f - threshold)) { - if (SCULPT_check_vertex_pivot_symmetry( - vd.co, ss->filter_cache->mask_expand_initial_co, symm)) { - add_v3_v3(avg, vd.co); - total++; - } - } - } - BKE_pbvh_vertex_iter_end; - } - - if (total > 0) { - mul_v3_fl(avg, 1.0f / total); - copy_v3_v3(ss->pivot_pos, avg); - } - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); - } - - MEM_freeN(op->customdata); - - for (int i = 0; i < ss->filter_cache->totnode; i++) { - BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]); - } - - SCULPT_filter_cache_free(ss); - - SCULPT_undo_push_end(); - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); - ED_workspace_status_text(C, NULL); - return OPERATOR_FINISHED; - } - - /* When pressing Ctrl, expand directly to the max number of iterations. This allows to flood fill - * mask and face sets by connectivity directly. */ - if (event->ctrl) { - mask_expand_update_it = ss->filter_cache->mask_update_last_it - 1; - } - - if (!ELEM(event->type, MOUSEMOVE, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY)) { - return OPERATOR_RUNNING_MODAL; - } - - if (mask_expand_update_it == ss->filter_cache->mask_update_current_it) { - ED_region_tag_redraw(region); - return OPERATOR_RUNNING_MODAL; - } - - if (mask_expand_update_it < ss->filter_cache->mask_update_last_it) { - - if (create_face_set) { - for (int i = 0; i < ss->totfaces; i++) { - ss->face_sets[i] = ss->filter_cache->prev_face_set[i]; - } - } - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .nodes = ss->filter_cache->nodes, - .mask_expand_update_it = mask_expand_update_it, - .mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"), - .mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"), - .mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"), - .mask_expand_create_face_set = RNA_boolean_get(op->ptr, "create_face_set"), - }; - PBVHParallelSettings settings; - BKE_pbvh_parallel_range_settings( - &settings, (sd->flags & SCULPT_USE_OPENMP), ss->filter_cache->totnode); - BKE_pbvh_parallel_range(0, ss->filter_cache->totnode, &data, sculpt_expand_task_cb, &settings); - ss->filter_cache->mask_update_current_it = mask_expand_update_it; - } - - SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); - - return OPERATOR_RUNNING_MODAL; -} - -typedef struct MaskExpandFloodFillData { - float original_normal[3]; - float edge_sensitivity; - bool use_normals; -} MaskExpandFloodFillData; - -static bool mask_expand_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) -{ - MaskExpandFloodFillData *data = userdata; - - if (!is_duplicate) { - int to_it = ss->filter_cache->mask_update_it[from_v] + 1; - ss->filter_cache->mask_update_it[to_v] = to_it; - if (to_it > ss->filter_cache->mask_update_last_it) { - ss->filter_cache->mask_update_last_it = to_it; - } - - if (data->use_normals) { - float current_normal[3], prev_normal[3]; - SCULPT_vertex_normal_get(ss, to_v, current_normal); - SCULPT_vertex_normal_get(ss, from_v, prev_normal); - const float from_edge_factor = ss->filter_cache->edge_factor[from_v]; - ss->filter_cache->edge_factor[to_v] = dot_v3v3(current_normal, prev_normal) * - from_edge_factor; - ss->filter_cache->normal_factor[to_v] = dot_v3v3(data->original_normal, current_normal) * - powf(from_edge_factor, data->edge_sensitivity); - CLAMP(ss->filter_cache->normal_factor[to_v], 0.0f, 1.0f); - } - } - else { - /* PBVH_GRIDS duplicate handling. */ - ss->filter_cache->mask_update_it[to_v] = ss->filter_cache->mask_update_it[from_v]; - if (data->use_normals) { - ss->filter_cache->edge_factor[to_v] = ss->filter_cache->edge_factor[from_v]; - ss->filter_cache->normal_factor[to_v] = ss->filter_cache->normal_factor[from_v]; - } - } - - return true; -} - -static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - PBVH *pbvh = ob->sculpt->pbvh; - - const bool use_normals = RNA_boolean_get(op->ptr, "use_normals"); - const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set"); - - SculptCursorGeometryInfo sgi; - float mouse[2]; - mouse[0] = event->mval[0]; - mouse[1] = event->mval[1]; - - SCULPT_vertex_random_access_init(ss); - - op->customdata = MEM_mallocN(2 * sizeof(float), "initial mouse position"); - copy_v2_v2(op->customdata, mouse); - - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); - - int vertex_count = SCULPT_vertex_count_get(ss); - - ss->filter_cache = MEM_callocN(sizeof(FilterCache), "filter cache"); - - BKE_pbvh_search_gather(pbvh, NULL, NULL, &ss->filter_cache->nodes, &ss->filter_cache->totnode); - - SCULPT_undo_push_begin("Mask Expand"); - - if (create_face_set) { - SCULPT_undo_push_node(ob, ss->filter_cache->nodes[0], SCULPT_UNDO_FACE_SETS); - for (int i = 0; i < ss->filter_cache->totnode; i++) { - BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]); - } - } - else { - for (int i = 0; i < ss->filter_cache->totnode; i++) { - SCULPT_undo_push_node(ob, ss->filter_cache->nodes[i], SCULPT_UNDO_MASK); - BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]); - } - } - - ss->filter_cache->mask_update_it = MEM_callocN(sizeof(int) * vertex_count, - "mask update iteration"); - if (use_normals) { - ss->filter_cache->normal_factor = MEM_callocN(sizeof(float) * vertex_count, - "mask update normal factor"); - ss->filter_cache->edge_factor = MEM_callocN(sizeof(float) * vertex_count, - "mask update normal factor"); - for (int i = 0; i < vertex_count; i++) { - ss->filter_cache->edge_factor[i] = 1.0f; - } - } - - if (create_face_set) { - ss->filter_cache->prev_face_set = MEM_callocN(sizeof(float) * ss->totfaces, "prev face mask"); - for (int i = 0; i < ss->totfaces; i++) { - ss->filter_cache->prev_face_set[i] = ss->face_sets[i]; - } - ss->filter_cache->new_face_set = SCULPT_face_set_next_available_get(ss); - } - else { - ss->filter_cache->prev_mask = MEM_callocN(sizeof(float) * vertex_count, "prev mask"); - for (int i = 0; i < vertex_count; i++) { - ss->filter_cache->prev_mask[i] = SCULPT_vertex_mask_get(ss, i); - } - } - - ss->filter_cache->mask_update_last_it = 1; - ss->filter_cache->mask_update_current_it = 1; - ss->filter_cache->mask_update_it[SCULPT_active_vertex_get(ss)] = 0; - - copy_v3_v3(ss->filter_cache->mask_expand_initial_co, SCULPT_active_vertex_co_get(ss)); - - SculptFloodFill flood; - SCULPT_floodfill_init(ss, &flood); - SCULPT_floodfill_add_active(sd, ob, ss, &flood, FLT_MAX); - - MaskExpandFloodFillData fdata = { - .use_normals = use_normals, - .edge_sensitivity = RNA_int_get(op->ptr, "edge_sensitivity"), - }; - SCULPT_active_vertex_normal_get(ss, fdata.original_normal); - SCULPT_floodfill_execute(ss, &flood, mask_expand_floodfill_cb, &fdata); - SCULPT_floodfill_free(&flood); - - if (use_normals) { - for (int repeat = 0; repeat < 2; repeat++) { - for (int i = 0; i < vertex_count; i++) { - float avg = 0.0f; - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { - avg += ss->filter_cache->normal_factor[ni.index]; - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - ss->filter_cache->normal_factor[i] = avg / ni.size; - } - } - - MEM_SAFE_FREE(ss->filter_cache->edge_factor); - } - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .nodes = ss->filter_cache->nodes, - .mask_expand_update_it = 0, - .mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"), - .mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"), - .mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"), - .mask_expand_create_face_set = RNA_boolean_get(op->ptr, "create_face_set"), - }; - PBVHParallelSettings settings; - BKE_pbvh_parallel_range_settings( - &settings, (sd->flags & SCULPT_USE_OPENMP), ss->filter_cache->totnode); - BKE_pbvh_parallel_range(0, ss->filter_cache->totnode, &data, sculpt_expand_task_cb, &settings); - - const char *status_str = TIP_( - "Move the mouse to expand the mask from the active vertex. LMB: confirm mask, ESC/RMB: " - "cancel"); - ED_workspace_status_text(C, status_str); - - SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); - WM_event_add_modal_handler(C, op); - return OPERATOR_RUNNING_MODAL; -} - -static void SCULPT_OT_mask_expand(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Mask Expand"; - ot->idname = "SCULPT_OT_mask_expand"; - ot->description = "Expands a mask from the initial active vertex under the cursor"; - - /* API callbacks. */ - ot->invoke = sculpt_mask_expand_invoke; - ot->modal = sculpt_mask_expand_modal; - ot->cancel = sculpt_mask_expand_cancel; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - ot->prop = RNA_def_boolean(ot->srna, "invert", true, "Invert", "Invert the new mask"); - ot->prop = RNA_def_boolean( - ot->srna, "use_cursor", true, "Use Cursor", "Expand the mask to the cursor position"); - ot->prop = RNA_def_boolean(ot->srna, - "update_pivot", - true, - "Update Pivot Position", - "Set the pivot position to the mask border after creating the mask"); - ot->prop = RNA_def_int(ot->srna, "smooth_iterations", 2, 0, 10, "Smooth iterations", "", 0, 10); - ot->prop = RNA_def_int(ot->srna, "mask_speed", 5, 1, 10, "Mask speed", "", 1, 10); - - ot->prop = RNA_def_boolean(ot->srna, - "use_normals", - true, - "Use Normals", - "Generate the mask using the normals and curvature of the model"); - ot->prop = RNA_def_boolean(ot->srna, - "keep_previous_mask", - false, - "Keep Previous Mask", - "Generate the new mask on top of the current one"); - ot->prop = RNA_def_int(ot->srna, - "edge_sensitivity", - 300, - 0, - 2000, - "Edge Detection Sensitivity", - "Sensitivity for expanding the mask across sculpted sharp edges when " - "using normals to generate the mask", - 0, - 2000); - ot->prop = RNA_def_boolean(ot->srna, - "create_face_set", - false, - "Expand Face Mask", - "Expand a new Face Mask instead of the sculpt mask"); -} - void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); diff --git a/source/blender/editors/sculpt_paint/sculpt_automasking.c b/source/blender/editors/sculpt_paint/sculpt_automasking.c index 768b25a9a22..68fa7ac01ae 100644 --- a/source/blender/editors/sculpt_paint/sculpt_automasking.c +++ b/source/blender/editors/sculpt_paint/sculpt_automasking.c @@ -63,8 +63,8 @@ #include <stdlib.h> bool SCULPT_is_automasking_mode_enabled(const Sculpt *sd, - const Brush *br, - const eAutomasking_flag mode) + const Brush *br, + const eAutomasking_flag mode) { return br->automasking_flags & mode || sd->automasking_flags & mode; } @@ -205,9 +205,9 @@ static float *sculpt_face_sets_automasking_init(Sculpt *sd, Object *ob, float *a #define EDGE_DISTANCE_INF -1 float *SCULPT_boundary_automasking_init(Object *ob, - eBoundaryAutomaskMode mode, - int propagation_steps, - float *automask_factor) + eBoundaryAutomaskMode mode, + int propagation_steps, + float *automask_factor) { SculptSession *ss = ob->sculpt; diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mask.c b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c new file mode 100644 index 00000000000..d895ecc7119 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c @@ -0,0 +1,501 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_task.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_brush.h" +#include "BKE_context.h" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_interface.h" + +#include "bmesh.h" + +#include <math.h> +#include <stdlib.h> + +typedef enum eSculptMaskFilterTypes { + MASK_FILTER_SMOOTH = 0, + MASK_FILTER_SHARPEN = 1, + MASK_FILTER_GROW = 2, + MASK_FILTER_SHRINK = 3, + MASK_FILTER_CONTRAST_INCREASE = 5, + MASK_FILTER_CONTRAST_DECREASE = 6, +} eSculptMaskFilterTypes; + +static EnumPropertyItem prop_mask_filter_types[] = { + {MASK_FILTER_SMOOTH, "SMOOTH", 0, "Smooth Mask", "Smooth mask"}, + {MASK_FILTER_SHARPEN, "SHARPEN", 0, "Sharpen Mask", "Sharpen mask"}, + {MASK_FILTER_GROW, "GROW", 0, "Grow Mask", "Grow mask"}, + {MASK_FILTER_SHRINK, "SHRINK", 0, "Shrink Mask", "Shrink mask"}, + {MASK_FILTER_CONTRAST_INCREASE, + "CONTRAST_INCREASE", + 0, + "Increase contrast", + "Increase the contrast of the paint mask"}, + {MASK_FILTER_CONTRAST_DECREASE, + "CONTRAST_DECREASE", + 0, + "Decrease contrast", + "Decrease the contrast of the paint mask"}, + {0, NULL, 0, NULL, NULL}, +}; + +static void mask_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]; + bool update = false; + + const int mode = data->filter_type; + float contrast = 0.0f; + + PBVHVertexIter vd; + + if (mode == MASK_FILTER_CONTRAST_INCREASE) { + contrast = 0.1f; + } + + if (mode == MASK_FILTER_CONTRAST_DECREASE) { + contrast = -0.1f; + } + + BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) + { + float delta, gain, offset, max, min; + float prev_val = *vd.mask; + SculptVertexNeighborIter ni; + switch (mode) { + case MASK_FILTER_SMOOTH: + case MASK_FILTER_SHARPEN: { + float val = SCULPT_neighbor_mask_average(ss, vd.index); + + val -= *vd.mask; + + if (mode == MASK_FILTER_SMOOTH) { + *vd.mask += val; + } + else if (mode == MASK_FILTER_SHARPEN) { + if (*vd.mask > 0.5f) { + *vd.mask += 0.05f; + } + else { + *vd.mask -= 0.05f; + } + *vd.mask += val / 2.0f; + } + break; + } + case MASK_FILTER_GROW: + max = 0.0f; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + float vmask_f = data->prev_mask[ni.index]; + if (vmask_f > max) { + max = vmask_f; + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + *vd.mask = max; + break; + case MASK_FILTER_SHRINK: + min = 1.0f; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + float vmask_f = data->prev_mask[ni.index]; + if (vmask_f < min) { + min = vmask_f; + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + *vd.mask = min; + break; + case MASK_FILTER_CONTRAST_INCREASE: + case MASK_FILTER_CONTRAST_DECREASE: + delta = contrast / 2.0f; + gain = 1.0f - delta * 2.0f; + if (contrast > 0) { + gain = 1.0f / ((gain != 0.0f) ? gain : FLT_EPSILON); + offset = gain * (-delta); + } + else { + delta *= -1.0f; + offset = gain * (delta); + } + *vd.mask = gain * (*vd.mask) + offset; + break; + } + CLAMP(*vd.mask, 0.0f, 1.0f); + if (*vd.mask != prev_val) { + update = true; + } + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + + if (update) { + BKE_pbvh_node_mark_update_mask(node); + } +} + +static int sculpt_mask_filter_exec(bContext *C, wmOperator *op) +{ + ARegion *region = CTX_wm_region(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + int totnode; + int filter_type = RNA_enum_get(op->ptr, "filter_type"); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); + + SCULPT_vertex_random_access_init(ss); + + if (!ob->sculpt->pmap) { + return OPERATOR_CANCELLED; + } + + int num_verts = SCULPT_vertex_count_get(ss); + + BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); + SCULPT_undo_push_begin("Mask filter"); + + for (int i = 0; i < totnode; i++) { + SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK); + } + + float *prev_mask = NULL; + int iterations = RNA_int_get(op->ptr, "iterations"); + + /* Auto iteration count calculates the number of iteration based on the vertices of the mesh to + * avoid adding an unnecessary amount of undo steps when using the operator from a shortcut. + * One iteration per 50000 vertices in the mesh should be fine in most cases. + * Maybe we want this to be configurable. */ + if (RNA_boolean_get(op->ptr, "auto_iteration_count")) { + iterations = (int)(num_verts / 50000.0f) + 1; + } + + for (int i = 0; i < iterations; i++) { + if (ELEM(filter_type, MASK_FILTER_GROW, MASK_FILTER_SHRINK)) { + prev_mask = MEM_mallocN(num_verts * sizeof(float), "prevmask"); + for (int j = 0; j < num_verts; j++) { + prev_mask[j] = SCULPT_vertex_mask_get(ss, j); + } + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = nodes, + .filter_type = filter_type, + .prev_mask = prev_mask, + }; + + PBVHParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode); + BKE_pbvh_parallel_range(0, totnode, &data, mask_filter_task_cb, &settings); + + if (ELEM(filter_type, MASK_FILTER_GROW, MASK_FILTER_SHRINK)) { + MEM_freeN(prev_mask); + } + } + + MEM_SAFE_FREE(nodes); + + SCULPT_undo_push_end(); + + ED_region_tag_redraw(region); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + return OPERATOR_FINISHED; +} + +void SCULPT_mask_filter_smooth_apply(Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, const int smooth_iterations) { + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = nodes, + .filter_type = MASK_FILTER_SMOOTH, + }; + + for (int i = 0; i < smooth_iterations; i++) { + PBVHParallelSettings settings; + BKE_pbvh_parallel_range_settings( + &settings, (sd->flags & SCULPT_USE_OPENMP), totnode); + BKE_pbvh_parallel_range(0, totnode, &data, mask_filter_task_cb, &settings); + } +} + +void SCULPT_OT_mask_filter(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Mask Filter"; + ot->idname = "SCULPT_OT_mask_filter"; + ot->description = "Applies a filter to modify the current mask"; + + /* API callbacks. */ + ot->exec = sculpt_mask_filter_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER; + + /* RNA. */ + RNA_def_enum(ot->srna, + "filter_type", + prop_mask_filter_types, + MASK_FILTER_SMOOTH, + "Type", + "Filter that is going to be applied to the mask"); + RNA_def_int(ot->srna, + "iterations", + 1, + 1, + 100, + "Iterations", + "Number of times that the filter is going to be applied", + 1, + 100); + RNA_def_boolean( + ot->srna, + "auto_iteration_count", + false, + "Auto Iteration Count", + "Use a automatic number of iterations based on the number of vertices of the sculpt"); +} + +static float neighbor_dirty_mask(SculptSession *ss, PBVHVertexIter *vd) +{ + int total = 0; + float avg[3]; + zero_v3(avg); + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) { + float normalized[3]; + sub_v3_v3v3(normalized, SCULPT_vertex_co_get(ss, ni.index), vd->co); + normalize_v3(normalized); + add_v3_v3(avg, normalized); + total++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + if (total > 0) { + mul_v3_fl(avg, 1.0f / total); + float normal[3]; + if (vd->no) { + normal_short_to_float_v3(normal, vd->no); + } + else { + copy_v3_v3(normal, vd->fno); + } + float dot = dot_v3v3(avg, normal); + float angle = max_ff(saacosf(dot), 0.0f); + return angle; + } + return 0.0f; +} + +typedef struct DirtyMaskRangeData { + float min, max; +} DirtyMaskRangeData; + +static void dirty_mask_compute_range_task_cb(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + PBVHNode *node = data->nodes[i]; + DirtyMaskRangeData *range = tls->userdata_chunk; + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) + { + float dirty_mask = neighbor_dirty_mask(ss, &vd); + range->min = min_ff(dirty_mask, range->min); + range->max = max_ff(dirty_mask, range->max); + } + BKE_pbvh_vertex_iter_end; +} + +static void dirty_mask_compute_range_reduce(const void *__restrict UNUSED(userdata), + void *__restrict chunk_join, + void *__restrict chunk) +{ + DirtyMaskRangeData *join = chunk_join; + DirtyMaskRangeData *range = chunk; + join->min = min_ff(range->min, join->min); + join->max = max_ff(range->max, join->max); +} + +static void dirty_mask_apply_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; + + const bool dirty_only = data->dirty_mask_dirty_only; + const float min = data->dirty_mask_min; + const float max = data->dirty_mask_max; + + float range = max - min; + if (range < 0.0001f) { + range = 0.0f; + } + else { + range = 1.0f / range; + } + + BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) + { + float dirty_mask = neighbor_dirty_mask(ss, &vd); + float mask = *vd.mask + (1.0f - ((dirty_mask - min) * range)); + if (dirty_only) { + mask = fminf(mask, 0.5f) * 2.0f; + } + *vd.mask = CLAMPIS(mask, 0.0f, 1.0f); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + BKE_pbvh_node_mark_update_mask(node); +} + +static int sculpt_dirty_mask_exec(bContext *C, wmOperator *op) +{ + ARegion *region = CTX_wm_region(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + int totnode; + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); + + SCULPT_vertex_random_access_init(ss); + + if (!ob->sculpt->pmap) { + return OPERATOR_CANCELLED; + } + + BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); + SCULPT_undo_push_begin("Dirty Mask"); + + for (int i = 0; i < totnode; i++) { + SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK); + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = nodes, + .dirty_mask_dirty_only = RNA_boolean_get(op->ptr, "dirty_only"), + }; + DirtyMaskRangeData range = { + .min = FLT_MAX, + .max = -FLT_MAX, + }; + + PBVHParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode); + + settings.func_reduce = dirty_mask_compute_range_reduce; + settings.userdata_chunk = ⦥ + settings.userdata_chunk_size = sizeof(DirtyMaskRangeData); + + BKE_pbvh_parallel_range(0, totnode, &data, dirty_mask_compute_range_task_cb, &settings); + data.dirty_mask_min = range.min; + data.dirty_mask_max = range.max; + BKE_pbvh_parallel_range(0, totnode, &data, dirty_mask_apply_task_cb, &settings); + + MEM_SAFE_FREE(nodes); + + BKE_pbvh_update_vertex_data(pbvh, PBVH_UpdateMask); + + SCULPT_undo_push_end(); + + ED_region_tag_redraw(region); + + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_dirty_mask(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Dirty Mask"; + ot->idname = "SCULPT_OT_dirty_mask"; + ot->description = "Generates a mask based on the geometry cavity and pointiness"; + + /* API callbacks. */ + ot->exec = sculpt_dirty_mask_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER; + + /* RNA. */ + RNA_def_boolean( + ot->srna, "dirty_only", false, "Dirty Only", "Don't calculate cleans for convex areas"); +} diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c index b1dd7b87507..f64e8ea609b 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c @@ -316,12 +316,12 @@ static void mesh_filter_task_cb(void *__restrict userdata, } case MESH_FILTER_SURFACE_SMOOTH: { SCULPT_surface_smooth_laplacian_step(ss, - disp, - vd.co, - ss->filter_cache->surface_smooth_laplacian_disp, - vd.index, - orig_data.co, - ss->filter_cache->surface_smooth_shape_preservation); + disp, + vd.co, + ss->filter_cache->surface_smooth_laplacian_disp, + vd.index, + orig_data.co, + ss->filter_cache->surface_smooth_shape_preservation); break; } } @@ -365,11 +365,11 @@ static void mesh_filter_surface_smooth_displace_task_cb( continue; } SCULPT_surface_smooth_displace_step(ss, - vd.co, - ss->filter_cache->surface_smooth_laplacian_disp, - vd.index, - ss->filter_cache->surface_smooth_current_vertex, - clamp_f(fade, 0.0f, 1.0f)); + vd.co, + ss->filter_cache->surface_smooth_laplacian_disp, + vd.index, + ss->filter_cache->surface_smooth_current_vertex, + clamp_f(fade, 0.0f, 1.0f)); } BKE_pbvh_vertex_iter_end; } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 7c4142a807e..3858a990e97 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -82,6 +82,7 @@ void SCULPT_vertex_random_access_init(struct SculptSession *ss); int SCULPT_vertex_count_get(struct SculptSession *ss); const float *SCULPT_vertex_co_get(struct SculptSession *ss, int index); +void SCULPT_vertex_normal_get(SculptSession *ss, int index, float no[3]); float SCULPT_vertex_mask_get(struct SculptSession *ss, int index); #define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256 @@ -132,6 +133,7 @@ void SCULPT_vertex_neighbors_get(struct SculptSession *ss, int SCULPT_active_vertex_get(SculptSession *ss); const float *SCULPT_active_vertex_co_get(SculptSession *ss); +void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]); bool SCULPT_vertex_is_boundary(SculptSession *ss, const int index); @@ -276,6 +278,9 @@ float *SCULPT_boundary_automasking_init(Object *ob, void SCULPT_filter_cache_init(Object *ob, Sculpt *sd); void SCULPT_filter_cache_free(SculptSession *ss); +void SCULPT_mask_filter_smooth_apply( + Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, const int smooth_iterations); + /* Brushes. */ /* Cloth Brush. */ @@ -839,4 +844,11 @@ void SCULPT_OT_set_pivot_position(struct wmOperatorType *ot); /* Mesh Filter. */ void SCULPT_OT_mesh_filter(struct wmOperatorType *ot); +/* Mask filter and Dirty Mask. */ +void SCULPT_OT_mask_filter(struct wmOperatorType *ot); +void SCULPT_OT_dirty_mask(struct wmOperatorType *ot); + +/* Mask and Face Sets Expand. */ +void SCULPT_OT_mask_expand(struct wmOperatorType *ot); + #endif diff --git a/source/blender/editors/sculpt_paint/sculpt_mask_expand.c b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c new file mode 100644 index 00000000000..2d4fc2b0258 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c @@ -0,0 +1,526 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_task.h" + +#include "BLT_translation.h" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_mesh.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_view3d.h" +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "bmesh.h" + +#include <math.h> +#include <stdlib.h> + + +static void sculpt_mask_expand_cancel(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set"); + + MEM_freeN(op->customdata); + + for (int n = 0; n < ss->filter_cache->totnode; n++) { + PBVHNode *node = ss->filter_cache->nodes[n]; + if (create_face_set) { + for (int i = 0; i < ss->totfaces; i++) { + ss->face_sets[i] = ss->filter_cache->prev_face_set[i]; + } + } + else { + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) + { + *vd.mask = ss->filter_cache->prev_mask[vd.index]; + } + BKE_pbvh_vertex_iter_end; + } + + BKE_pbvh_node_mark_redraw(node); + } + + if (!create_face_set) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); + } + SCULPT_filter_cache_free(ss); + SCULPT_undo_push_end(); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); + ED_workspace_status_text(C, NULL); +} + +static void sculpt_expand_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; + int update_it = data->mask_expand_update_it; + + BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_ALL) + { + int vi = vd.index; + float final_mask = *vd.mask; + if (data->mask_expand_use_normals) { + if (ss->filter_cache->normal_factor[SCULPT_active_vertex_get(ss)] < + ss->filter_cache->normal_factor[vd.index]) { + final_mask = 1.0f; + } + else { + final_mask = 0.0f; + } + } + else { + if (ss->filter_cache->mask_update_it[vi] <= update_it && + ss->filter_cache->mask_update_it[vi] != 0) { + final_mask = 1.0f; + } + else { + final_mask = 0.0f; + } + } + + if (data->mask_expand_create_face_set) { + if (final_mask == 1.0f) { + SCULPT_vertex_face_set_set(ss, vd.index, ss->filter_cache->new_face_set); + } + BKE_pbvh_node_mark_redraw(node); + } + else { + + if (data->mask_expand_keep_prev_mask) { + final_mask = MAX2(ss->filter_cache->prev_mask[vd.index], final_mask); + } + + if (data->mask_expand_invert_mask) { + final_mask = 1.0f - final_mask; + } + + if (*vd.mask != final_mask) { + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + *vd.mask = final_mask; + BKE_pbvh_node_mark_update_mask(node); + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + ARegion *region = CTX_wm_region(C); + float prevclick_f[2]; + copy_v2_v2(prevclick_f, op->customdata); + int prevclick[2] = {(int)prevclick_f[0], (int)prevclick_f[1]}; + int len = (int)len_v2v2_int(prevclick, event->mval); + len = abs(len); + int mask_speed = RNA_int_get(op->ptr, "mask_speed"); + int mask_expand_update_it = len / mask_speed; + mask_expand_update_it = mask_expand_update_it + 1; + + const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set"); + + if (RNA_boolean_get(op->ptr, "use_cursor")) { + SculptCursorGeometryInfo sgi; + float mouse[2]; + mouse[0] = event->mval[0]; + mouse[1] = event->mval[1]; + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + mask_expand_update_it = ss->filter_cache->mask_update_it[(int)SCULPT_active_vertex_get(ss)]; + } + + if ((event->type == EVT_ESCKEY && event->val == KM_PRESS) || + (event->type == RIGHTMOUSE && event->val == KM_PRESS)) { + /* Returning OPERATOR_CANCELLED will leak memory due to not finishing + * undo. Better solution could be to make paint_mesh_restore_co work + * for this case. */ + sculpt_mask_expand_cancel(C, op); + return OPERATOR_FINISHED; + } + + if ((event->type == LEFTMOUSE && event->val == KM_RELEASE) || + (event->type == EVT_RETKEY && event->val == KM_PRESS) || + (event->type == EVT_PADENTER && event->val == KM_PRESS)) { + + /* Smooth iterations. */ + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false); + const int smooth_iterations = RNA_int_get(op->ptr, "smooth_iterations"); + SCULPT_mask_filter_smooth_apply(sd, ob, ss->filter_cache->nodes, ss->filter_cache->totnode, smooth_iterations); + + /* Pivot position. */ + if (RNA_boolean_get(op->ptr, "update_pivot")) { + const char symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; + const float threshold = 0.2f; + float avg[3]; + int total = 0; + zero_v3(avg); + + for (int n = 0; n < ss->filter_cache->totnode; n++) { + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, ss->filter_cache->nodes[n], vd, PBVH_ITER_UNIQUE) + { + const float mask = (vd.mask) ? *vd.mask : 0.0f; + if (mask < (0.5f + threshold) && mask > (0.5f - threshold)) { + if (SCULPT_check_vertex_pivot_symmetry( + vd.co, ss->filter_cache->mask_expand_initial_co, symm)) { + add_v3_v3(avg, vd.co); + total++; + } + } + } + BKE_pbvh_vertex_iter_end; + } + + if (total > 0) { + mul_v3_fl(avg, 1.0f / total); + copy_v3_v3(ss->pivot_pos, avg); + } + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); + } + + MEM_freeN(op->customdata); + + for (int i = 0; i < ss->filter_cache->totnode; i++) { + BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]); + } + + SCULPT_filter_cache_free(ss); + + SCULPT_undo_push_end(); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); + ED_workspace_status_text(C, NULL); + return OPERATOR_FINISHED; + } + + /* When pressing Ctrl, expand directly to the max number of iterations. This allows to flood fill + * mask and face sets by connectivity directly. */ + if (event->ctrl) { + mask_expand_update_it = ss->filter_cache->mask_update_last_it - 1; + } + + if (!ELEM(event->type, MOUSEMOVE, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY)) { + return OPERATOR_RUNNING_MODAL; + } + + if (mask_expand_update_it == ss->filter_cache->mask_update_current_it) { + ED_region_tag_redraw(region); + return OPERATOR_RUNNING_MODAL; + } + + if (mask_expand_update_it < ss->filter_cache->mask_update_last_it) { + + if (create_face_set) { + for (int i = 0; i < ss->totfaces; i++) { + ss->face_sets[i] = ss->filter_cache->prev_face_set[i]; + } + } + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + .mask_expand_update_it = mask_expand_update_it, + .mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"), + .mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"), + .mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"), + .mask_expand_create_face_set = RNA_boolean_get(op->ptr, "create_face_set"), + }; + PBVHParallelSettings settings; + BKE_pbvh_parallel_range_settings( + &settings, (sd->flags & SCULPT_USE_OPENMP), ss->filter_cache->totnode); + BKE_pbvh_parallel_range(0, ss->filter_cache->totnode, &data, sculpt_expand_task_cb, &settings); + ss->filter_cache->mask_update_current_it = mask_expand_update_it; + } + + SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); + + return OPERATOR_RUNNING_MODAL; +} + +typedef struct MaskExpandFloodFillData { + float original_normal[3]; + float edge_sensitivity; + bool use_normals; +} MaskExpandFloodFillData; + +static bool mask_expand_floodfill_cb( + SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) +{ + MaskExpandFloodFillData *data = userdata; + + if (!is_duplicate) { + int to_it = ss->filter_cache->mask_update_it[from_v] + 1; + ss->filter_cache->mask_update_it[to_v] = to_it; + if (to_it > ss->filter_cache->mask_update_last_it) { + ss->filter_cache->mask_update_last_it = to_it; + } + + if (data->use_normals) { + float current_normal[3], prev_normal[3]; + SCULPT_vertex_normal_get(ss, to_v, current_normal); + SCULPT_vertex_normal_get(ss, from_v, prev_normal); + const float from_edge_factor = ss->filter_cache->edge_factor[from_v]; + ss->filter_cache->edge_factor[to_v] = dot_v3v3(current_normal, prev_normal) * + from_edge_factor; + ss->filter_cache->normal_factor[to_v] = dot_v3v3(data->original_normal, current_normal) * + powf(from_edge_factor, data->edge_sensitivity); + CLAMP(ss->filter_cache->normal_factor[to_v], 0.0f, 1.0f); + } + } + else { + /* PBVH_GRIDS duplicate handling. */ + ss->filter_cache->mask_update_it[to_v] = ss->filter_cache->mask_update_it[from_v]; + if (data->use_normals) { + ss->filter_cache->edge_factor[to_v] = ss->filter_cache->edge_factor[from_v]; + ss->filter_cache->normal_factor[to_v] = ss->filter_cache->normal_factor[from_v]; + } + } + + return true; +} + +static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + PBVH *pbvh = ob->sculpt->pbvh; + + const bool use_normals = RNA_boolean_get(op->ptr, "use_normals"); + const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set"); + + SculptCursorGeometryInfo sgi; + float mouse[2]; + mouse[0] = event->mval[0]; + mouse[1] = event->mval[1]; + + SCULPT_vertex_random_access_init(ss); + + op->customdata = MEM_mallocN(2 * sizeof(float), "initial mouse position"); + copy_v2_v2(op->customdata, mouse); + + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); + + int vertex_count = SCULPT_vertex_count_get(ss); + + ss->filter_cache = MEM_callocN(sizeof(FilterCache), "filter cache"); + + BKE_pbvh_search_gather(pbvh, NULL, NULL, &ss->filter_cache->nodes, &ss->filter_cache->totnode); + + SCULPT_undo_push_begin("Mask Expand"); + + if (create_face_set) { + SCULPT_undo_push_node(ob, ss->filter_cache->nodes[0], SCULPT_UNDO_FACE_SETS); + for (int i = 0; i < ss->filter_cache->totnode; i++) { + BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]); + } + } + else { + for (int i = 0; i < ss->filter_cache->totnode; i++) { + SCULPT_undo_push_node(ob, ss->filter_cache->nodes[i], SCULPT_UNDO_MASK); + BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]); + } + } + + ss->filter_cache->mask_update_it = MEM_callocN(sizeof(int) * vertex_count, + "mask update iteration"); + if (use_normals) { + ss->filter_cache->normal_factor = MEM_callocN(sizeof(float) * vertex_count, + "mask update normal factor"); + ss->filter_cache->edge_factor = MEM_callocN(sizeof(float) * vertex_count, + "mask update normal factor"); + for (int i = 0; i < vertex_count; i++) { + ss->filter_cache->edge_factor[i] = 1.0f; + } + } + + if (create_face_set) { + ss->filter_cache->prev_face_set = MEM_callocN(sizeof(float) * ss->totfaces, "prev face mask"); + for (int i = 0; i < ss->totfaces; i++) { + ss->filter_cache->prev_face_set[i] = ss->face_sets[i]; + } + ss->filter_cache->new_face_set = SCULPT_face_set_next_available_get(ss); + } + else { + ss->filter_cache->prev_mask = MEM_callocN(sizeof(float) * vertex_count, "prev mask"); + for (int i = 0; i < vertex_count; i++) { + ss->filter_cache->prev_mask[i] = SCULPT_vertex_mask_get(ss, i); + } + } + + ss->filter_cache->mask_update_last_it = 1; + ss->filter_cache->mask_update_current_it = 1; + ss->filter_cache->mask_update_it[SCULPT_active_vertex_get(ss)] = 0; + + copy_v3_v3(ss->filter_cache->mask_expand_initial_co, SCULPT_active_vertex_co_get(ss)); + + SculptFloodFill flood; + SCULPT_floodfill_init(ss, &flood); + SCULPT_floodfill_add_active(sd, ob, ss, &flood, FLT_MAX); + + MaskExpandFloodFillData fdata = { + .use_normals = use_normals, + .edge_sensitivity = RNA_int_get(op->ptr, "edge_sensitivity"), + }; + SCULPT_active_vertex_normal_get(ss, fdata.original_normal); + SCULPT_floodfill_execute(ss, &flood, mask_expand_floodfill_cb, &fdata); + SCULPT_floodfill_free(&flood); + + if (use_normals) { + for (int repeat = 0; repeat < 2; repeat++) { + for (int i = 0; i < vertex_count; i++) { + float avg = 0.0f; + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { + avg += ss->filter_cache->normal_factor[ni.index]; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + ss->filter_cache->normal_factor[i] = avg / ni.size; + } + } + + MEM_SAFE_FREE(ss->filter_cache->edge_factor); + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + .mask_expand_update_it = 0, + .mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"), + .mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"), + .mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"), + .mask_expand_create_face_set = RNA_boolean_get(op->ptr, "create_face_set"), + }; + PBVHParallelSettings settings; + BKE_pbvh_parallel_range_settings( + &settings, (sd->flags & SCULPT_USE_OPENMP), ss->filter_cache->totnode); + BKE_pbvh_parallel_range(0, ss->filter_cache->totnode, &data, sculpt_expand_task_cb, &settings); + + const char *status_str = TIP_( + "Move the mouse to expand the mask from the active vertex. LMB: confirm mask, ESC/RMB: " + "cancel"); + ED_workspace_status_text(C, status_str); + + SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +void SCULPT_OT_mask_expand(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Mask Expand"; + ot->idname = "SCULPT_OT_mask_expand"; + ot->description = "Expands a mask from the initial active vertex under the cursor"; + + /* API callbacks. */ + ot->invoke = sculpt_mask_expand_invoke; + ot->modal = sculpt_mask_expand_modal; + ot->cancel = sculpt_mask_expand_cancel; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->prop = RNA_def_boolean(ot->srna, "invert", true, "Invert", "Invert the new mask"); + ot->prop = RNA_def_boolean( + ot->srna, "use_cursor", true, "Use Cursor", "Expand the mask to the cursor position"); + ot->prop = RNA_def_boolean(ot->srna, + "update_pivot", + true, + "Update Pivot Position", + "Set the pivot position to the mask border after creating the mask"); + ot->prop = RNA_def_int(ot->srna, "smooth_iterations", 2, 0, 10, "Smooth iterations", "", 0, 10); + ot->prop = RNA_def_int(ot->srna, "mask_speed", 5, 1, 10, "Mask speed", "", 1, 10); + + ot->prop = RNA_def_boolean(ot->srna, + "use_normals", + true, + "Use Normals", + "Generate the mask using the normals and curvature of the model"); + ot->prop = RNA_def_boolean(ot->srna, + "keep_previous_mask", + false, + "Keep Previous Mask", + "Generate the new mask on top of the current one"); + ot->prop = RNA_def_int(ot->srna, + "edge_sensitivity", + 300, + 0, + 2000, + "Edge Detection Sensitivity", + "Sensitivity for expanding the mask across sculpted sharp edges when " + "using normals to generate the mask", + 0, + 2000); + ot->prop = RNA_def_boolean(ot->srna, + "create_face_set", + false, + "Expand Face Mask", + "Expand a new Face Mask instead of the sculpt mask"); +} diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.c b/source/blender/editors/sculpt_paint/sculpt_smooth.c index 0f629320333..de3211abe08 100644 --- a/source/blender/editors/sculpt_paint/sculpt_smooth.c +++ b/source/blender/editors/sculpt_paint/sculpt_smooth.c @@ -62,8 +62,6 @@ #include <math.h> #include <stdlib.h> - - /* For the smooth brush, uses the neighboring vertices around vert to calculate * a smoothed location for vert. Skips corner vertices (used by only one * polygon). */ @@ -185,7 +183,8 @@ void SCULPT_bmesh_four_neighbor_average(float avg[3], float direction[3], BMVert } } -/* Generic functions for laplacian smoothing. These functions do not take boundary vertices into account. */ +/* Generic functions for laplacian smoothing. These functions do not take boundary vertices into + * account. */ void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], int index) { @@ -498,9 +497,8 @@ void SCULPT_surface_smooth_displace_step(SculptSession *ss, } } -static void SCULPT_do_surface_smooth_brush_laplacian_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) +static void SCULPT_do_surface_smooth_brush_laplacian_task_cb_ex( + void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; @@ -528,21 +526,20 @@ static void SCULPT_do_surface_smooth_brush_laplacian_task_cb_ex(void *__restrict float disp[3]; SCULPT_surface_smooth_laplacian_step(ss, - disp, - vd.co, - ss->cache->surface_smooth_laplacian_disp, - vd.index, - orig_data.co, - alpha); + disp, + vd.co, + ss->cache->surface_smooth_laplacian_disp, + vd.index, + orig_data.co, + alpha); madd_v3_v3fl(vd.co, disp, clamp_f(fade, 0.0f, 1.0f)); } BKE_pbvh_vertex_iter_end; } } -static void SCULPT_do_surface_smooth_brush_displace_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) +static void SCULPT_do_surface_smooth_brush_displace_task_cb_ex( + void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; @@ -599,5 +596,3 @@ void SCULPT_do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, in 0, totnode, &data, SCULPT_do_surface_smooth_brush_displace_task_cb_ex, &settings); } } - - |