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/editors/sculpt_paint/sculpt_filter_mask.c | |
parent | f2f30db98dacf2821fce3389952798c597bffe11 (diff) |
Cleanup: Move Mask Filter and Mask Expand to their own files
Diffstat (limited to 'source/blender/editors/sculpt_paint/sculpt_filter_mask.c')
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_filter_mask.c | 501 |
1 files changed, 501 insertions, 0 deletions
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"); +} |