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:
authorPablo Dobarro <pablodp606@gmail.com>2020-04-03 22:46:08 +0300
committerPablo Dobarro <pablodp606@gmail.com>2020-04-03 22:46:08 +0300
commit17931f3b5125528e3d763ae8a80eae5e4730dcc3 (patch)
treef0917bab236dd01a2c0929492bfa0b013f956e80 /source/blender
parentf2f30db98dacf2821fce3389952798c597bffe11 (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.txt2
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c893
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_automasking.c10
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_mask.c501
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_mesh.c22
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h12
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_mask_expand.c526
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_smooth.c29
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 = &range;
- 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 = &range;
+ 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);
}
}
-
-