Sculpt: Surface Smooth Brush and Mesh Filter

This implements the Surface Smooth Brush as a mode inside the Smooth tool,
which uses the HC algorithm from "Improved Laplacian Smoothing of Noisy Surface Meshes".
Comparted to the regular smooth brush with laplacian smooth, this brush removes
the surface while preserving the volume of the object.
The smooth result can be controlled by tweaing the original shape preservation,
displacement and iteration count.
The same surface smooth operation is also available as a mesh filter.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D7057
This commit is contained in:
Pablo Dobarro 2020-03-26 16:05:46 +01:00 committed by Pablo Dobarro
parent a0437c3f73
commit a218be3080
7 changed files with 326 additions and 8 deletions

View File

@ -665,6 +665,14 @@ def brush_settings(layout, context, brush, popover=False):
col.prop(brush, "use_multiplane_scrape_dynamic")
col.prop(brush, "show_multiplane_scrape_planes_preview")
if brush.sculpt_tool == 'SMOOTH':
col = layout.column()
col.prop(brush, "smooth_deform_type")
if brush.smooth_deform_type == 'SURFACE':
col.prop(brush, "surface_smooth_shape_preservation")
col.prop(brush, "surface_smooth_current_vertex")
col.prop(brush, "surface_smooth_iterations")
if brush.sculpt_tool == 'MASK':
layout.row().prop(brush, "mask_tool", expand=True)

View File

@ -1031,6 +1031,9 @@ class _defs_sculpt:
layout.prop(props, "strength")
layout.prop(props, "deform_axis")
layout.prop(props, "use_face_sets")
if (props.type == "SURFACE_SMOOTH"):
layout.prop(props, "surface_smooth_shape_preservation", expand=False)
layout.prop(props, "surface_smooth_current_vertex", expand=False)
return dict(
idname="builtin.mesh_filter",

View File

@ -1348,6 +1348,9 @@ void BKE_brush_sculpt_reset(Brush *br)
br->flag &= ~BRUSH_SPACE_ATTEN;
br->spacing = 5;
br->alpha = 0.7f;
br->surface_smooth_shape_preservation = 0.5f;
br->surface_smooth_current_vertex = 0.5f;
br->surface_smooth_iterations = 4;
break;
case SCULPT_TOOL_SNAKE_HOOK:
br->alpha = 1.0f;

View File

@ -914,6 +914,7 @@ static bool sculpt_tool_needs_original(const char sculpt_tool)
SCULPT_TOOL_LAYER,
SCULPT_TOOL_DRAW_SHARP,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_SMOOTH,
SCULPT_TOOL_POSE);
}
@ -2859,7 +2860,7 @@ static float bmesh_neighbor_average_mask(BMVert *v, const int cd_vert_mask_offse
}
}
static void grids_neighbor_average(SculptSession *ss, float result[3], int index)
static void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], int index)
{
float avg[3] = {0.0f, 0.0f, 0.0f};
int total = 0;
@ -3155,7 +3156,7 @@ static void do_smooth_brush_multires_task_cb_ex(void *__restrict userdata,
}
else {
float avg[3], val[3];
grids_neighbor_average(ss, avg, vd.index);
SCULPT_neighbor_coords_average(ss, avg, vd.index);
sub_v3_v3v3(val, avg, vd.co);
madd_v3_v3v3fl(val, vd.co, val, fade);
sculpt_clip(sd, ss, vd.co, val);
@ -3254,6 +3255,153 @@ static void do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnod
SculptSession *ss = ob->sculpt;
smooth(sd, ob, nodes, totnode, ss->cache->bstrength, false);
}
/* HC Smooth Algorithm. */
/* From: Improved Laplacian Smoothing of Noisy Surface Meshes */
static void surface_smooth_laplacian_step(SculptSession *ss,
float *disp,
const float co[3],
float (*laplacian_disp)[3],
const int v_index,
const float origco[3],
const float alpha)
{
float laplacian_smooth_co[3];
float weigthed_o[3], weigthed_q[3], d[3];
SCULPT_neighbor_coords_average(ss, laplacian_smooth_co, v_index);
mul_v3_v3fl(weigthed_o, origco, alpha);
mul_v3_v3fl(weigthed_q, co, 1.0f - alpha);
add_v3_v3v3(d, weigthed_o, weigthed_q);
sub_v3_v3v3(laplacian_disp[v_index], laplacian_smooth_co, d);
sub_v3_v3v3(disp, laplacian_smooth_co, co);
}
static void surface_smooth_displace_step(SculptSession *ss,
float *co,
float (*laplacian_disp)[3],
const int v_index,
const float beta,
const float fade)
{
float b_avg[3] = {0.0f, 0.0f, 0.0f};
float b_current_vertex[3];
int total = 0;
SculptVertexNeighborIter ni;
sculpt_vertex_neighbors_iter_begin(ss, v_index, ni)
{
add_v3_v3(b_avg, laplacian_disp[ni.index]);
total++;
}
sculpt_vertex_neighbors_iter_end(ni);
if (total > 0) {
mul_v3_v3fl(b_current_vertex, b_avg, (1.0f - beta) / (float)total);
madd_v3_v3fl(b_current_vertex, laplacian_disp[v_index], beta);
mul_v3_fl(b_current_vertex, clamp_f(fade, 0.0f, 1.0f));
sub_v3_v3(co, b_current_vertex);
}
}
static void 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;
const Brush *brush = data->brush;
const float bstrength = ss->cache->bstrength;
float alpha = brush->surface_smooth_shape_preservation;
PBVHVertexIter vd;
SculptOrigVertData orig_data;
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
SCULPT_orig_vert_data_update(&orig_data, &vd);
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
const float fade =
bstrength *
SCULPT_brush_strength_factor(
ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, tls->thread_id);
float disp[3];
surface_smooth_laplacian_step(ss,
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 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;
const Brush *brush = data->brush;
const float bstrength = ss->cache->bstrength;
const float beta = brush->surface_smooth_current_vertex;
PBVHVertexIter vd;
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
const float fade =
bstrength *
SCULPT_brush_strength_factor(
ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, tls->thread_id);
surface_smooth_displace_step(
ss, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.index, beta, fade);
}
}
BKE_pbvh_vertex_iter_end;
}
static void do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
Brush *brush = BKE_paint_brush(&sd->paint);
SculptSession *ss = ob->sculpt;
if (ss->cache->mirror_symmetry_pass == 0 && ss->cache->radial_symmetry_pass == 0) {
ss->cache->surface_smooth_laplacian_disp = MEM_callocN(
SCULPT_vertex_count_get(ss) * 3 * sizeof(float), "HC smooth laplacian b");
}
/* Threaded loop over nodes. */
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
};
PBVHParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
for (int i = 0; i < brush->surface_smooth_iterations; i++) {
BKE_pbvh_parallel_range(
0, totnode, &data, do_surface_smooth_brush_laplacian_task_cb_ex, &settings);
BKE_pbvh_parallel_range(
0, totnode, &data, do_surface_smooth_brush_displace_task_cb_ex, &settings);
}
}
static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata,
const int n,
@ -6010,7 +6158,12 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
do_draw_brush(sd, ob, nodes, totnode);
break;
case SCULPT_TOOL_SMOOTH:
do_smooth_brush(sd, ob, nodes, totnode);
if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) {
do_smooth_brush(sd, ob, nodes, totnode);
}
else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) {
do_surface_smooth_brush(sd, ob, nodes, totnode);
}
break;
case SCULPT_TOOL_CREASE:
do_crease_brush(sd, ob, nodes, totnode);
@ -9178,7 +9331,9 @@ static void sculpt_filter_cache_free(SculptSession *ss)
if (ss->filter_cache->automask) {
MEM_freeN(ss->filter_cache->automask);
}
if (ss->filter_cache->surface_smooth_laplacian_disp) {
MEM_freeN(ss->filter_cache->surface_smooth_laplacian_disp);
}
MEM_freeN(ss->filter_cache);
ss->filter_cache = NULL;
}
@ -9191,6 +9346,7 @@ typedef enum eSculptMeshFilterTypes {
MESH_FILTER_RANDOM = 4,
MESH_FILTER_RELAX = 5,
MESH_FILTER_RELAX_FACE_SETS = 6,
MESH_FILTER_SURFACE_SMOOTH = 7,
} eSculptMeshFilterTypes;
static EnumPropertyItem prop_mesh_filter_types[] = {
@ -9205,6 +9361,11 @@ static EnumPropertyItem prop_mesh_filter_types[] = {
0,
"Relax Face Sets",
"Smooth the edges of all the Face Sets"},
{MESH_FILTER_SURFACE_SMOOTH,
"SURFACE_SMOOTH",
0,
"Surface Smooth",
"Smooth the surface of the mesh, preserving the volume"},
{0, NULL, 0, NULL, NULL},
};
@ -9223,8 +9384,11 @@ static EnumPropertyItem prop_mesh_filter_deform_axis_items[] = {
static bool sculpt_mesh_filter_needs_pmap(int filter_type, bool use_face_sets)
{
return use_face_sets ||
ELEM(filter_type, MESH_FILTER_SMOOTH, MESH_FILTER_RELAX, MESH_FILTER_RELAX_FACE_SETS);
return use_face_sets || ELEM(filter_type,
MESH_FILTER_SMOOTH,
MESH_FILTER_RELAX,
MESH_FILTER_RELAX_FACE_SETS,
MESH_FILTER_SURFACE_SMOOTH);
}
static void mesh_filter_task_cb(void *__restrict userdata,
@ -9296,7 +9460,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
bmesh_neighbor_average(avg, vd.bm_vert);
break;
case PBVH_GRIDS:
grids_neighbor_average(ss, avg, vd.index);
SCULPT_neighbor_coords_average(ss, avg, vd.index);
break;
}
sub_v3_v3v3(val, avg, orig_co);
@ -9357,6 +9521,16 @@ static void mesh_filter_task_cb(void *__restrict userdata,
sub_v3_v3v3(disp, val, vd.co);
break;
}
case MESH_FILTER_SURFACE_SMOOTH: {
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);
break;
}
}
for (int it = 0; it < 3; it++) {
@ -9365,7 +9539,12 @@ static void mesh_filter_task_cb(void *__restrict userdata,
}
}
add_v3_v3v3(final_pos, orig_co, disp);
if (filter_type == MESH_FILTER_SURFACE_SMOOTH) {
madd_v3_v3v3fl(final_pos, vd.co, disp, clamp_f(fade, 0.0f, 1.0f));
}
else {
add_v3_v3v3(final_pos, orig_co, disp);
}
copy_v3_v3(vd.co, final_pos);
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
@ -9376,6 +9555,32 @@ static void mesh_filter_task_cb(void *__restrict userdata,
BKE_pbvh_node_mark_update(node);
}
static void mesh_filter_surface_smooth_displace_task_cb(
void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
PBVHNode *node = data->nodes[i];
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE)
{
float fade = vd.mask ? *vd.mask : 0.0f;
fade = 1.0f - fade;
fade *= data->filter_strength;
if (fade == 0.0f) {
continue;
}
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));
}
BKE_pbvh_vertex_iter_end;
}
static int sculpt_mesh_filter_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
Object *ob = CTX_data_active_object(C);
@ -9418,6 +9623,14 @@ static int sculpt_mesh_filter_modal(bContext *C, wmOperator *op, const wmEvent *
&settings, (sd->flags & SCULPT_USE_OPENMP), ss->filter_cache->totnode);
BKE_pbvh_parallel_range(0, ss->filter_cache->totnode, &data, mesh_filter_task_cb, &settings);
if (filter_type == MESH_FILTER_SURFACE_SMOOTH) {
BKE_pbvh_parallel_range(0,
ss->filter_cache->totnode,
&data,
mesh_filter_surface_smooth_displace_task_cb,
&settings);
}
ss->filter_cache->iteration_count++;
if (ss->deform_modifiers_active || ss->shapekey_active) {
@ -9480,6 +9693,15 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
ss->filter_cache->active_face_set = SCULPT_FACE_SET_NONE;
}
if (RNA_enum_get(op->ptr, "type") == MESH_FILTER_SURFACE_SMOOTH) {
ss->filter_cache->surface_smooth_laplacian_disp = MEM_mallocN(
3 * sizeof(float) * SCULPT_vertex_count_get(ss), "surface smooth disp");
ss->filter_cache->surface_smooth_shape_preservation = RNA_float_get(
op->ptr, "surface_smooth_shape_preservation");
ss->filter_cache->surface_smooth_current_vertex = RNA_float_get(
op->ptr, "surface_smooth_current_vertex");
}
ss->filter_cache->enabled_axis[0] = deform_axis & MESH_FILTER_DEFORM_X;
ss->filter_cache->enabled_axis[1] = deform_axis & MESH_FILTER_DEFORM_Y;
ss->filter_cache->enabled_axis[2] = deform_axis & MESH_FILTER_DEFORM_Z;
@ -9532,6 +9754,26 @@ static void SCULPT_OT_mesh_filter(struct wmOperatorType *ot)
false,
"Use Face Sets",
"Apply the filter only to the Face Mask under the cursor");
/* Surface Smooth Mesh Filter properties. */
RNA_def_float(ot->srna,
"surface_smooth_shape_preservation",
0.5f,
0.0f,
1.0f,
"Shape Preservation",
"How much of the original shape is preserved when smoothing",
0.0f,
1.0f);
RNA_def_float(ot->srna,
"surface_smooth_current_vertex",
0.5f,
0.0f,
1.0f,
"Per Vertex Displacement",
"How much the position of each individual vertex influences the final result",
0.0f,
1.0f);
}
typedef enum eSculptMaskFilterTypes {

View File

@ -618,6 +618,10 @@ typedef struct StrokeCache {
float initial_normal[3];
float true_initial_normal[3];
/* Surface Smooth Brush */
/* Stores the displacement produced by the laplacian step of HC smooth. */
float (*surface_smooth_laplacian_disp)[3];
float vertex_rotation; /* amount to rotate the vertices when using rotate brush */
struct Dial *dial;
@ -650,6 +654,11 @@ typedef struct FilterCache {
* achieve certain effects. */
int iteration_count;
/* Stores the displacement produced by the laplacian step of HC smooth. */
float (*surface_smooth_laplacian_disp)[3];
float surface_smooth_shape_preservation;
float surface_smooth_current_vertex;
/* unmasked nodes */
PBVHNode **nodes;
int totnode;

View File

@ -281,6 +281,11 @@ typedef enum eBrushClothDeformType {
BRUSH_CLOTH_DEFORM_EXPAND = 6,
} eBrushClothDeformType;
typedef enum eBrushSmoothDeformType {
BRUSH_SMOOTH_DEFORM_LAPLACIAN = 0,
BRUSH_SMOOTH_DEFORM_SURFACE = 1,
} eBrushSmoothDeformType;
typedef enum eBrushClothForceFalloffType {
BRUSH_CLOTH_FORCE_FALLOFF_RADIAL = 0,
BRUSH_CLOTH_FORCE_FALLOFF_PLANE = 1,
@ -473,6 +478,12 @@ typedef struct Brush {
float cloth_sim_limit;
float cloth_sim_falloff;
/* smooth */
int smooth_deform_type;
float surface_smooth_shape_preservation;
float surface_smooth_current_vertex;
int surface_smooth_iterations;
/* multiplane scrape */
float multiplane_scrape_angle;

View File

@ -1794,6 +1794,20 @@ static void rna_def_brush(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_smooth_deform_type_items[] = {
{BRUSH_SMOOTH_DEFORM_LAPLACIAN,
"LAPLACIAN",
0,
"Laplacian",
"Smooths the surface and the volume"},
{BRUSH_SMOOTH_DEFORM_SURFACE,
"SURFACE",
0,
"Surface",
"Smooths the surface of the mesh, preserving the volue"},
{0, NULL, 0, NULL, NULL},
};
srna = RNA_def_struct(brna, "Brush", "ID");
RNA_def_struct_ui_text(
srna, "Brush", "Brush data-block for storing brush settings for painting and sculpting");
@ -1909,6 +1923,11 @@ static void rna_def_brush(BlenderRNA *brna)
prop, "Force Falloff", "Shape used in the brush to apply force to the cloth");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "smooth_deform_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_smooth_deform_type_items);
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "jitter_unit", PROP_ENUM, PROP_NONE); /* as an enum */
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, brush_jitter_unit_items);
@ -2101,6 +2120,29 @@ static void rna_def_brush(BlenderRNA *brna)
prop, "Pose Origin Offset", "Offset of the pose origin in relation to the brush radius");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "surface_smooth_shape_preservation", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "surface_smooth_shape_preservation");
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_text(
prop, "Shape Preservation", "How much of the original shape is preserved when smoothing");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "surface_smooth_current_vertex", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "surface_smooth_current_vertex");
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_text(
prop,
"Per Vertex Displacement",
"How much the position of each individual vertex influences the final result");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "surface_smooth_iterations", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, NULL, "surface_smooth_iterations");
RNA_def_property_range(prop, 1, 10);
RNA_def_property_ui_range(prop, 1, 10, 1, 3);
RNA_def_property_ui_text(prop, "Iterations", "Number of smoothing iterations per brush step");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "multiplane_scrape_angle", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "multiplane_scrape_angle");
RNA_def_property_range(prop, 0.0f, 160.0f);