* Added a "projection" option to smooth/autosmooth. It works by

projection neighboring vertices onto the current smooth vert's normal
  plane, multiplied by a "projection" factor.  This is extremely similar
  to what the surface laplacian produces, but is much simpler, uses
  no temporary state and thus can be used in more places.
This commit is contained in:
Joseph Eagar 2021-05-02 10:54:12 -07:00
parent f70a8c1581
commit 2d98802905
10 changed files with 157 additions and 50 deletions

View File

@ -564,7 +564,22 @@ def brush_settings(layout, context, brush, popover=False):
pressure_name="use_inverse_smooth_pressure",
slider=True,
)
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"auto_smooth_projection",
slider=True,
)
elif brush.sculpt_tool == "SMOOTH":
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"auto_smooth_projection",
slider=True,
)
if capabilities.has_vcol_boundary_smooth:
layout.prop(brush, "vcol_boundary_factor", slider=True)

View File

@ -447,6 +447,7 @@ static void brush_defaults(Brush *brush)
FROM_DEFAULT(alpha);
FROM_DEFAULT(hardness);
FROM_DEFAULT(autosmooth_factor);
FROM_DEFAULT(autosmooth_projection);
FROM_DEFAULT(topology_rake_factor);
FROM_DEFAULT(crease_pinch_factor);
FROM_DEFAULT(normal_radius_factor);
@ -1651,6 +1652,7 @@ void BKE_brush_debug_print_state(Brush *br)
BR_TEST(plane_offset, f);
BR_TEST(autosmooth_factor, f);
BR_TEST(autosmooth_projection, f);
BR_TEST(topology_rake_factor, f);

View File

@ -19,22 +19,23 @@
*/
/*
TODO:
Convergence improvements:
1. DONE: Limit number of edges processed per run.
2. Scale split steps by ratio of long to short edges to
2. DONE: Scale split steps by ratio of long to short edges to
prevent runaway tesselation.
3. Detect and dissolve three and four valence vertices that are surrounded by
3. DONE: Detect and dissolve three and four valence vertices that are surrounded by
all tris.
4. Use different (coarser) brush spacing for applying dyntopo
4. DONE: Use different (coarser) brush spacing for applying dyntopo
Drawing improvements:
4. Build and cache vertex index buffers, to reduce GPU bandwidth
4. PARTIAL DONE: Build and cache vertex index buffers, to reduce GPU bandwidth
Topology rake:
5. Enable new curvature topology rake code and add to UI.
6. Add code to cache curvature data per vertex in a CD layer.
5. DONE: Enable new curvature topology rake code and add to UI.
6. DONE: Add code to cache curvature data per vertex in a CD layer.
*/

View File

@ -3493,7 +3493,7 @@ static void do_mask_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
do_mask_brush_draw(sd, ob, nodes, totnode);
break;
case BRUSH_MASK_SMOOTH:
SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, true);
SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, true, 0.0f);
break;
}
}
@ -6614,7 +6614,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
break;
case SCULPT_TOOL_SMOOTH:
if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) {
SCULPT_do_smooth_brush(sd, ob, nodes, totnode);
SCULPT_do_smooth_brush(sd, ob, nodes, totnode, brush->autosmooth_projection);
}
else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) {
SCULPT_do_surface_smooth_brush(sd, ob, nodes, totnode);
@ -6726,10 +6726,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
brush->autosmooth_factor > 0) {
if (brush->flag & BRUSH_INVERSE_SMOOTH_PRESSURE) {
SCULPT_smooth(
sd, ob, nodes, totnode, brush->autosmooth_factor * (1.0f - ss->cache->pressure), false);
sd, ob, nodes, totnode, brush->autosmooth_factor * (1.0f - ss->cache->pressure), false, brush->autosmooth_projection);
}
else {
SCULPT_smooth(sd, ob, nodes, totnode, brush->autosmooth_factor, false);
SCULPT_smooth(sd, ob, nodes, totnode, brush->autosmooth_factor, false, brush->autosmooth_projection);
}
}

View File

@ -334,7 +334,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
switch (filter_type) {
case MESH_FILTER_SMOOTH:
fade = clamp_f(fade, -1.0f, 1.0f);
SCULPT_neighbor_coords_average_interior(ss, avg, vd.vertex);
SCULPT_neighbor_coords_average_interior(ss, avg, vd.vertex, 0.0f);
sub_v3_v3v3(val, avg, orig_co);
madd_v3_v3v3fl(val, orig_co, val, fade);
sub_v3_v3v3(disp, val, orig_co);
@ -399,7 +399,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
ss->filter_cache->surface_smooth_laplacian_disp,
vd.vertex,
orig_data.co,
ss->filter_cache->surface_smooth_shape_preservation);
ss->filter_cache->surface_smooth_shape_preservation, 0.0f);
break;
}
case MESH_FILTER_SHARPEN: {
@ -424,7 +424,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
float disp_avg[3];
float avg_co[3];
SCULPT_neighbor_coords_average(ss, avg_co, vd.vertex);
SCULPT_neighbor_coords_average(ss, avg_co, vd.vertex, 0.0f);
sub_v3_v3v3(disp_avg, avg_co, vd.co);
mul_v3_v3fl(
disp_avg, disp_avg, smooth_ratio * pow2f(ss->filter_cache->sharpen_factor[vd.index]));
@ -489,7 +489,7 @@ static void mesh_filter_enhance_details_init_directions(SculptSession *ss)
SculptVertRef vertex = BKE_pbvh_table_index_to_vertex(ss->pbvh, i);
float avg[3];
SCULPT_neighbor_coords_average(ss, avg, vertex);
SCULPT_neighbor_coords_average(ss, avg, vertex, 0.0f);
sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex));
}
}
@ -540,7 +540,7 @@ static void mesh_filter_sharpen_init(SculptSession *ss,
float avg[3];
SculptVertRef vertex = BKE_pbvh_table_index_to_vertex(ss->pbvh, i);
SCULPT_neighbor_coords_average(ss, avg, vertex);
SCULPT_neighbor_coords_average(ss, avg, vertex, 0.0f);
sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex));
filter_cache->sharpen_factor[i] = len_v3(filter_cache->detail_directions[i]);
}

View File

@ -571,14 +571,18 @@ void SCULPT_do_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
/* Smooth Brush. */
void SCULPT_bmesh_four_neighbor_average(float avg[3], float direction[3], struct BMVert *v);
void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], SculptVertRef index);
void SCULPT_neighbor_coords_average(SculptSession *ss,
float result[3],
SculptVertRef index,
float projection);
float SCULPT_neighbor_mask_average(SculptSession *ss, SculptVertRef index);
void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], SculptVertRef index);
/* Mask the mesh boundaries smoothing only the mesh surface without using automasking. */
void SCULPT_neighbor_coords_average_interior(SculptSession *ss,
float result[3],
SculptVertRef index);
SculptVertRef index,
float projection);
void SCULPT_smooth_vcol_boundary(
Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength);
@ -588,8 +592,10 @@ void SCULPT_smooth(Sculpt *sd,
PBVHNode **nodes,
const int totnode,
float bstrength,
const bool smooth_mask);
void SCULPT_do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
const bool smooth_mask,
float projection);
void SCULPT_do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float projection);
/* Surface Smooth Brush. */
@ -599,7 +605,9 @@ void SCULPT_surface_smooth_laplacian_step(SculptSession *ss,
float (*laplacian_disp)[3],
const SculptVertRef v_index,
const float origco[3],
const float alpha);
const float alpha,
const float projection);
void SCULPT_surface_smooth_displace_step(SculptSession *ss,
float *co,
float (*laplacian_disp)[3],
@ -832,6 +840,8 @@ typedef struct SculptThreadedTaskData {
// Layer brush
int cd_pers_co, cd_pers_no, cd_pers_disp;
int cd_layer_disp;
float smooth_projection;
} SculptThreadedTaskData;
/*************** Brush testing declarations ****************/

View File

@ -24,6 +24,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_compiler_attrs.h"
#include "BLI_hash.h"
#include "BLI_math.h"
#include "BLI_task.h"
@ -69,44 +70,75 @@
void SCULPT_neighbor_coords_average_interior(SculptSession *ss,
float result[3],
SculptVertRef index)
SculptVertRef vertex,
float projection)
{
float avg[3] = {0.0f, 0.0f, 0.0f};
int total = 0;
int neighbor_count = 0;
const bool is_boundary = SCULPT_vertex_is_boundary(ss, index);
const bool is_boundary = SCULPT_vertex_is_boundary(ss, vertex);
const float *co = SCULPT_vertex_co_get(ss, vertex);
float no[3];
if (projection > 0.0f) {
SCULPT_vertex_normal_get(ss, vertex, no);
}
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) {
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
neighbor_count++;
float tmp[3];
bool ok = false;
if (is_boundary) {
/* Boundary vertices use only other boundary vertices. */
if (SCULPT_vertex_is_boundary(ss, ni.vertex)) {
add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex));
total++;
copy_v3_v3(tmp, SCULPT_vertex_co_get(ss, ni.vertex));
ok = true;
}
}
else {
/* Interior vertices use all neighbors. */
add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex));
total++;
copy_v3_v3(tmp, SCULPT_vertex_co_get(ss, ni.vertex));
ok = true;
}
if (!ok) {
continue;
}
if (projection > 0.0f) {
sub_v3_v3(tmp, co);
float fac = dot_v3v3(tmp, no);
madd_v3_v3fl(tmp, no, -fac * projection);
add_v3_v3(avg, tmp);
}
else {
add_v3_v3(avg, tmp);
}
total++;
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
/* Do not modify corner vertices. */
if (neighbor_count <= 2) {
copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex));
return;
}
/* Avoid division by 0 when there are no neighbors. */
if (total == 0) {
copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex));
return;
}
mul_v3_v3fl(result, avg, 1.0f / total);
if (projection > 0.0f) {
add_v3_v3(result, co);
}
}
/* For bmesh: Average surrounding verts based on an orthogonality measure.
@ -159,23 +191,49 @@ 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. */
void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], SculptVertRef index)
void SCULPT_neighbor_coords_average(SculptSession *ss,
float result[3],
SculptVertRef vertex,
float projection)
{
float avg[3] = {0.0f, 0.0f, 0.0f};
float *co, no[3];
int total = 0;
if (projection > 0.0f) {
co = (float *)SCULPT_vertex_co_get(ss, vertex);
SCULPT_vertex_normal_get(ss, vertex, no);
}
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) {
add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex));
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
const float *co2 = SCULPT_vertex_co_get(ss, ni.vertex);
if (projection > 0.0f) {
float tmp[3];
sub_v3_v3v3(tmp, co2, co);
float fac = dot_v3v3(tmp, no);
madd_v3_v3fl(tmp, no, -fac * projection);
add_v3_v3(avg, tmp);
}
else {
add_v3_v3(avg, co2);
}
total++;
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
if (total > 0) {
mul_v3_v3fl(result, avg, 1.0f / total);
if (projection > 0.0) {
add_v3_v3(result, co);
}
}
else {
copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex));
}
}
@ -282,7 +340,7 @@ static void SCULPT_enhance_details_brush(Sculpt *sd,
float avg[3];
SculptVertRef vertex = BKE_pbvh_table_index_to_vertex(ss->pbvh, i);
SCULPT_neighbor_coords_average(ss, avg, vertex);
SCULPT_neighbor_coords_average(ss, avg, vertex, 0.0f);
sub_v3_v3v3(ss->cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex));
}
}
@ -391,6 +449,7 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
const Brush *brush = data->brush;
const bool smooth_mask = data->smooth_mask;
float bstrength = data->strength;
float projection = data->smooth_projection;
PBVHVertexIter vd;
@ -424,7 +483,7 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
}
else {
float avg[3], val[3];
SCULPT_neighbor_coords_average_interior(ss, avg, vd.vertex);
SCULPT_neighbor_coords_average_interior(ss, avg, vd.vertex, projection);
sub_v3_v3v3(val, avg, vd.co);
madd_v3_v3v3fl(val, vd.co, val, fade);
SCULPT_clip(sd, ss, vd.co, val);
@ -442,7 +501,8 @@ void SCULPT_smooth(Sculpt *sd,
PBVHNode **nodes,
const int totnode,
float bstrength,
const bool smooth_mask)
const bool smooth_mask,
float projection)
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
@ -482,6 +542,7 @@ void SCULPT_smooth(Sculpt *sd,
.nodes = nodes,
.smooth_mask = smooth_mask,
.strength = strength,
.smooth_projection = projection,
};
TaskParallelSettings settings;
@ -494,7 +555,8 @@ void SCULPT_smooth(Sculpt *sd,
}
}
void SCULPT_do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
void SCULPT_do_smooth_brush(
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float projection)
{
SculptSession *ss = ob->sculpt;
if (ss->cache->bstrength <= 0.0f) {
@ -503,7 +565,7 @@ void SCULPT_do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnod
}
else {
/* Regular mode, smooth. */
SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, false);
SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, false, projection);
}
}
@ -516,11 +578,12 @@ void SCULPT_surface_smooth_laplacian_step(SculptSession *ss,
float (*laplacian_disp)[3],
const SculptVertRef v_index,
const float origco[3],
const float alpha)
const float alpha,
const float projection)
{
float laplacian_smooth_co[3];
float weigthed_o[3], weigthed_q[3], d[3];
SCULPT_neighbor_coords_average(ss, laplacian_smooth_co, v_index);
SCULPT_neighbor_coords_average(ss, laplacian_smooth_co, v_index, projection);
int index = BKE_pbvh_vertex_index_to_table(ss->pbvh, v_index);
@ -595,8 +658,14 @@ static void SCULPT_do_surface_smooth_brush_laplacian_task_cb_ex(
thread_id);
float disp[3];
SCULPT_surface_smooth_laplacian_step(
ss, disp, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.vertex, orig_data.co, alpha);
SCULPT_surface_smooth_laplacian_step(ss,
disp,
vd.co,
ss->cache->surface_smooth_laplacian_disp,
vd.vertex,
orig_data.co,
alpha,
data->smooth_projection);
madd_v3_v3fl(vd.co, disp, clamp_f(fade, 0.0f, 1.0f));
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
@ -652,12 +721,11 @@ void SCULPT_do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, in
}
/* Threaded loop over nodes. */
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
};
SculptThreadedTaskData data = {.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.smooth_projection = brush->autosmooth_projection};
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);

View File

@ -42,6 +42,7 @@
.size = 35, /* radius of the brush in pixels */ \
.alpha = 1.0f, /* brush strength/intensity probably variable should be renamed? */ \
.autosmooth_factor = 0.0f, \
.autosmooth_projection = 0.0f,\
.topology_rake_factor = 0.0f, \
.crease_pinch_factor = 0.5f, \
.normal_radius_factor = 0.5f, \

View File

@ -280,9 +280,10 @@ typedef struct Brush {
char gpencil_sculpt_tool;
/** Active grease pencil weight tool. */
char gpencil_weight_tool;
char _pad1[6];
char _pad1[2];
float autosmooth_factor;
float autosmooth_projection;
float tilt_strength_factor;

View File

@ -2987,6 +2987,15 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3);
RNA_def_property_ui_text(
prop, "Auto-Smooth", "Amount of smoothing to automatically apply to each stroke");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "auto_smooth_projection", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "autosmooth_projection");
RNA_def_property_float_default(prop, 0);
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3);
RNA_def_property_ui_text(
prop, "Projection", "How much autosmooth should stick to surface");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "concave_mask_factor", PROP_FLOAT, PROP_FACTOR);