Cleanup: Move Mask Filter and Mask Expand to their own files
This commit is contained in:
parent
f2f30db98d
commit
17931f3b51
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue