Fix T78747: Fix mesh boundary detection and automasking

This issue was produced by a hack in the sculpt mode code from 2.80
 when the sculpt API for connectivity info was not available.
The smooth brush was the only brush that needed connectivity info,
so there were 3 different smooth functions with the connectivity
queries implemented for dyntopo, meshes and grids. The mesh version
of smoothing was checking the number of connected faces to a vertex
to mask the mesh boundaries, which was not covering all cases and
was hardcoded in the smooth function itself.

This patch removes all those legacy functions and unifies all
smooth functions into a single one using the new API and the
automasking system. In order to achieve this, there were needed
some extra changes:

- The smooth brush now does not automasks the boundaries by default,
so its default preset needs to be updated to enable automasking

- The mesh boundary info is extracted once and cached in a
bitmap, similar to the disconnected elements IDs. This makes
boundary detection work as expected in all cases, solving a lot
of known issues with the smooth brush. In multires, this info is
extracted and cached only at the base mesh level, so it is much
more memory efficient than the previous automasking system.

- In order to keep the brushes responsive as they were before,
the automasking system can now skip creating the cache when it
is not needed for the requested options. This means that for
high poly meshes and simple automasking options the brushes
won't lag on start.

Reviewed By: sergey

Maniphest Tasks: T78747

Differential Revision: https://developer.blender.org/D8260
This commit is contained in:
Pablo Dobarro 2020-07-15 16:24:03 +02:00
parent 10cacbbb15
commit e06a346458
Notes: blender-bot 2023-02-14 06:46:23 +01:00
Referenced by issue #78747, Sculpting Smooth brush unexpected result with vertices with two edges
Referenced by issue #75237, Sculpting: Mesh Boundary Automasking doesn't effect concave corner vertices
8 changed files with 165 additions and 251 deletions

View File

@ -24,6 +24,7 @@
* \ingroup bke
*/
#include "BLI_bitmap.h"
#include "BLI_utildefines.h"
#include "DNA_object_enums.h"
@ -291,6 +292,9 @@ typedef struct SculptPersistentBase {
typedef struct SculptVertexInfo {
/* Idexed by vertex, stores and ID of its topologycally connected component. */
int *connected_component;
/* Indexed by base mesh vertex index, stores if that vertex is a boundary. */
BLI_bitmap *boundary;
} SculptVertexInfo;
typedef struct SculptFakeNeighbors {

View File

@ -1533,6 +1533,7 @@ void BKE_brush_sculpt_reset(Brush *br)
break;
case SCULPT_TOOL_SMOOTH:
br->flag &= ~BRUSH_SPACE_ATTEN;
br->automasking_flags |= BRUSH_AUTOMASKING_BOUNDARY_EDGES;
br->spacing = 5;
br->alpha = 0.7f;
br->surface_smooth_shape_preservation = 0.5f;

View File

@ -1369,6 +1369,8 @@ void BKE_sculptsession_free(Object *ob)
MEM_SAFE_FREE(ss->preview_vert_index_list);
MEM_SAFE_FREE(ss->vertex_info.connected_component);
MEM_SAFE_FREE(ss->vertex_info.boundary);
MEM_SAFE_FREE(ss->fake_neighbors.fake_neighbor_index);
if (ss->pose_ik_chain_preview) {

View File

@ -389,7 +389,7 @@ bool SCULPT_vertex_any_face_set_visible_get(SculptSession *ss, int index)
return true;
}
bool SCULPT_vertex_all_face_sets_visible_get(SculptSession *ss, int index)
bool SCULPT_vertex_all_face_sets_visible_get(const SculptSession *ss, int index)
{
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES: {
@ -788,28 +788,13 @@ void SCULPT_vertex_neighbors_get(SculptSession *ss,
}
}
static bool sculpt_check_boundary_vertex_in_base_mesh(SculptSession *ss, const int index)
static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss, const int index)
{
const MeshElemMap *vert_map = &ss->pmap[index];
if (vert_map->count <= 1) {
return true;
}
for (int i = 0; i < vert_map->count; i++) {
const MPoly *p = &ss->mpoly[vert_map->indices[i]];
unsigned f_adj_v[2];
if (poly_get_adj_loops_from_vert(p, ss->mloop, index, f_adj_v) != -1) {
int j;
for (j = 0; j < ARRAY_SIZE(f_adj_v); j += 1) {
if (!(vert_map->count != 2 || ss->pmap[f_adj_v[j]].count <= 2)) {
return true;
}
}
}
}
return false;
BLI_assert(ss->vertex_info.boundary);
return BLI_BITMAP_TEST(ss->vertex_info.boundary, index);
}
bool SCULPT_vertex_is_boundary(SculptSession *ss, const int index)
bool SCULPT_vertex_is_boundary(const SculptSession *ss, const int index)
{
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES: {
@ -8488,6 +8473,37 @@ static void sculpt_connected_components_ensure(Object *ob)
}
}
void SCULPT_boundary_info_ensure(Object *object)
{
SculptSession *ss = object->sculpt;
if (ss->vertex_info.boundary) {
return;
}
Mesh *base_mesh = BKE_mesh_from_object(object);
ss->vertex_info.boundary = BLI_BITMAP_NEW(base_mesh->totvert, "Boundary info");
int *adjacent_faces_edge_count = MEM_calloc_arrayN(
base_mesh->totedge, sizeof(int), "Adjacent face edge count");
for (int p = 0; p < base_mesh->totpoly; p++) {
MPoly *poly = &base_mesh->mpoly[p];
for (int l = 0; l < poly->totloop; l++) {
MLoop *loop = &base_mesh->mloop[l + poly->loopstart];
adjacent_faces_edge_count[loop->e]++;
}
}
for (int e = 0; e < base_mesh->totedge; e++) {
if (adjacent_faces_edge_count[e] < 2) {
MEdge *edge = &base_mesh->medge[e];
BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v1, true);
BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v2, true);
}
}
MEM_freeN(adjacent_faces_edge_count);
}
void SCULPT_fake_neighbors_ensure(Sculpt *sd, Object *ob, const float max_dist)
{
SculptSession *ss = ob->sculpt;

View File

@ -89,19 +89,65 @@ bool SCULPT_is_automasking_enabled(const Sculpt *sd, const SculptSession *ss, co
return false;
}
int SCULPT_automasking_mode_effective_bits(const Sculpt *sculpt, const Brush *brush)
{
return sculpt->automasking_flags | brush->automasking_flags;
}
static bool SCULPT_automasking_needs_cache(const Sculpt *sd, const Brush *brush)
{
const int automasking_flags = SCULPT_automasking_mode_effective_bits(sd, brush);
if (automasking_flags & BRUSH_AUTOMASKING_TOPOLOGY) {
return true;
}
if (automasking_flags & BRUSH_AUTOMASKING_BOUNDARY_EDGES) {
return brush->automasking_boundary_edges_propagation_steps != 1;
}
if (automasking_flags & BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS) {
return brush->automasking_boundary_edges_propagation_steps != 1;
}
return false;
}
float SCULPT_automasking_factor_get(SculptSession *ss, int vert)
{
if (ss->cache && ss->cache->automask) {
return ss->cache->automask[vert];
if (!ss->cache) {
return 1.0f;
}
/* If the cache is initialized with valid info, use the cache. This is used when the
* automasking information can't be computed in real time per vertex and needs to be
* initialized for the whole mesh when the stroke starts. */
if (ss->cache->automask_factor) {
return ss->cache->automask_factor[vert];
}
if (ss->cache->automask_settings.flags & BRUSH_AUTOMASKING_FACE_SETS) {
if (!SCULPT_vertex_has_face_set(ss, vert, ss->cache->automask_settings.initial_face_set)) {
return 0.0f;
}
}
if (ss->cache->automask_settings.flags & BRUSH_AUTOMASKING_BOUNDARY_EDGES) {
if (SCULPT_vertex_is_boundary(ss, vert)) {
return 0.0f;
}
}
if (ss->cache->automask_settings.flags & BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS) {
if (!SCULPT_vertex_has_unique_face_set(ss, vert)) {
return 0.0f;
}
}
return 1.0f;
}
void SCULPT_automasking_end(Object *ob)
{
SculptSession *ss = ob->sculpt;
if (ss->cache && ss->cache->automask) {
MEM_freeN(ss->cache->automask);
if (ss->cache && ss->cache->automask_factor) {
MEM_freeN(ss->cache->automask_factor);
}
}
@ -153,7 +199,7 @@ static float *SCULPT_topology_automasking_init(Sculpt *sd, Object *ob, float *au
const int totvert = SCULPT_vertex_count_get(ss);
for (int i = 0; i < totvert; i++) {
ss->cache->automask[i] = 0.0f;
ss->cache->automask_factor[i] = 0.0f;
}
/* Flood fill automask to connected vertices. Limited to vertices inside
@ -259,6 +305,14 @@ float *SCULPT_boundary_automasking_init(Object *ob,
return automask_factor;
}
static void SCULPT_stroke_automasking_settings_update(SculptSession *ss, Sculpt *sd, Brush *brush)
{
BLI_assert(ss->cache);
ss->cache->automask_settings.flags = SCULPT_automasking_mode_effective_bits(sd, brush);
ss->cache->automask_settings.initial_face_set = SCULPT_active_face_set_get(ss);
}
void SCULPT_automasking_init(Sculpt *sd, Object *ob)
{
SculptSession *ss = ob->sculpt;
@ -269,20 +323,26 @@ void SCULPT_automasking_init(Sculpt *sd, Object *ob)
return;
}
ss->cache->automask = MEM_callocN(sizeof(float) * SCULPT_vertex_count_get(ss),
"automask_factor");
SCULPT_stroke_automasking_settings_update(ss, sd, brush);
SCULPT_boundary_info_ensure(ob);
if (!SCULPT_automasking_needs_cache(sd, brush)) {
return;
}
ss->cache->automask_factor = MEM_malloc_arrayN(totvert, sizeof(float), "automask_factor");
for (int i = 0; i < totvert; i++) {
ss->cache->automask[i] = 1.0f;
ss->cache->automask_factor[i] = 1.0f;
}
if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_TOPOLOGY)) {
SCULPT_vertex_random_access_init(ss);
SCULPT_topology_automasking_init(sd, ob, ss->cache->automask);
SCULPT_topology_automasking_init(sd, ob, ss->cache->automask_factor);
}
if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_FACE_SETS)) {
SCULPT_vertex_random_access_init(ss);
sculpt_face_sets_automasking_init(sd, ob, ss->cache->automask);
sculpt_face_sets_automasking_init(sd, ob, ss->cache->automask_factor);
}
if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_BOUNDARY_EDGES)) {
@ -290,13 +350,13 @@ void SCULPT_automasking_init(Sculpt *sd, Object *ob)
SCULPT_boundary_automasking_init(ob,
AUTOMASK_INIT_BOUNDARY_EDGES,
brush->automasking_boundary_edges_propagation_steps,
ss->cache->automask);
ss->cache->automask_factor);
}
if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS)) {
SCULPT_vertex_random_access_init(ss);
SCULPT_boundary_automasking_init(ob,
AUTOMASK_INIT_BOUNDARY_FACE_SETS,
brush->automasking_boundary_edges_propagation_steps,
ss->cache->automask);
ss->cache->automask_factor);
}
}

View File

@ -261,17 +261,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
switch (filter_type) {
case MESH_FILTER_SMOOTH:
CLAMP(fade, -1.0f, 1.0f);
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES:
SCULPT_neighbor_average(ss, avg, vd.index);
break;
case PBVH_BMESH:
SCULPT_bmesh_neighbor_average(avg, vd.bm_vert);
break;
case PBVH_GRIDS:
SCULPT_neighbor_coords_average(ss, avg, vd.index);
break;
}
SCULPT_neighbor_coords_average_interior(ss, avg, vd.index);
sub_v3_v3v3(val, avg, orig_co);
madd_v3_v3v3fl(val, orig_co, val, fade);
sub_v3_v3v3(disp, val, orig_co);
@ -540,11 +530,14 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
SCULPT_vertex_random_access_init(ss);
bool needs_pmap = sculpt_mesh_filter_needs_pmap(filter_type, use_face_sets);
BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_pmap, false, false);
const bool needs_topology_info = sculpt_mesh_filter_needs_pmap(filter_type, use_face_sets);
BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_topology_info, false, false);
if (needs_topology_info) {
SCULPT_boundary_info_ensure(ob);
}
const int totvert = SCULPT_vertex_count_get(ss);
if (BKE_pbvh_type(pbvh) == PBVH_FACES && needs_pmap && !ob->sculpt->pmap) {
if (BKE_pbvh_type(pbvh) == PBVH_FACES && needs_topology_info && !ob->sculpt->pmap) {
return OPERATOR_CANCELLED;
}

View File

@ -148,8 +148,6 @@ 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);
/* Fake Neighbors */
#define FAKE_NEIGHBOR_NONE -1
@ -159,6 +157,11 @@ void SCULPT_fake_neighbors_enable(Object *ob);
void SCULPT_fake_neighbors_disable(Object *ob);
void SCULPT_fake_neighbors_free(struct Object *ob);
/* Vertex Info. */
void SCULPT_boundary_info_ensure(Object *object);
/* Boundary Info needs to be initialized in order to use this function. */
bool SCULPT_vertex_is_boundary(const SculptSession *ss, const int index);
/* Sculpt Visibility API */
void SCULPT_vertex_visible_set(SculptSession *ss, int index, bool visible);
@ -179,7 +182,7 @@ bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, int index);
int SCULPT_face_set_next_available_get(SculptSession *ss);
void SCULPT_face_set_visibility_set(SculptSession *ss, int face_set, bool visible);
bool SCULPT_vertex_all_face_sets_visible_get(SculptSession *ss, int index);
bool SCULPT_vertex_all_face_sets_visible_get(const SculptSession *ss, int index);
bool SCULPT_vertex_any_face_set_visible_get(SculptSession *ss, int index);
void SCULPT_face_sets_visibility_invert(SculptSession *ss);
@ -404,16 +407,15 @@ void SCULPT_do_paint_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
void SCULPT_do_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
/* Smooth Brush. */
void SCULPT_neighbor_average(SculptSession *ss, float avg[3], uint vert);
void SCULPT_bmesh_neighbor_average(float avg[3], struct BMVert *v);
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], int index);
float SCULPT_neighbor_mask_average(SculptSession *ss, int index);
void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], int index);
/* Mask the mesh boundaries smoothing only the mesh surface without using automasking. */
void SCULPT_neighbor_coords_average_interior(SculptSession *ss, float result[3], int index);
void SCULPT_smooth(Sculpt *sd,
Object *ob,
PBVHNode **nodes,
@ -735,6 +737,12 @@ bool SCULPT_pbvh_calc_area_normal(const struct Brush *brush,
#define SCULPT_CLAY_STABILIZER_LEN 10
typedef struct AutomaskingSettings {
/* Flags from eAutomasking_flag. */
int flags;
int initial_face_set;
} AutomaskingSettings;
typedef struct StrokeCache {
/* Invariants */
float initial_radius;
@ -857,7 +865,11 @@ typedef struct StrokeCache {
float true_gravity_direction[3];
float gravity_direction[3];
float *automask;
/* Automasking. */
AutomaskingSettings automask_settings;
/* Precomputed automask factor indexed by vertex, owned by the automasking system and initialized
* in SCULPT_automasking_init when needed. */
float *automask_factor;
float stroke_local_mat[4][4];
float multiplane_scrape_angle;

View File

@ -62,78 +62,29 @@
#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). */
void SCULPT_neighbor_average(SculptSession *ss, float avg[3], uint vert)
void SCULPT_neighbor_coords_average_interior(SculptSession *ss, float result[3], int index)
{
const MeshElemMap *vert_map = &ss->pmap[vert];
const MVert *mvert = ss->mvert;
float(*deform_co)[3] = ss->deform_cos;
float avg[3] = {0.0f, 0.0f, 0.0f};
int total = 0;
/* Don't modify corner vertices. */
if (vert_map->count > 1) {
int total = 0;
zero_v3(avg);
for (int i = 0; i < vert_map->count; i++) {
const MPoly *p = &ss->mpoly[vert_map->indices[i]];
uint f_adj_v[2];
if (poly_get_adj_loops_from_vert(p, ss->mloop, vert, f_adj_v) != -1) {
for (int j = 0; j < ARRAY_SIZE(f_adj_v); j += 1) {
if (vert_map->count != 2 || ss->pmap[f_adj_v[j]].count <= 2) {
add_v3_v3(avg, deform_co ? deform_co[f_adj_v[j]] : mvert[f_adj_v[j]].co);
total++;
}
}
}
}
if (total > 0) {
mul_v3_fl(avg, 1.0f / total);
return;
}
if (SCULPT_vertex_is_boundary(ss, index)) {
copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
return;
}
copy_v3_v3(avg, deform_co ? deform_co[vert] : mvert[vert].co);
}
/* Same logic as neighbor_average(), but for bmesh rather than mesh. */
void SCULPT_bmesh_neighbor_average(float avg[3], BMVert *v)
{
/* logic for 3 or more is identical. */
const int vfcount = BM_vert_face_count_at_most(v, 3);
/* Don't modify corner vertices. */
if (vfcount > 1) {
BMIter liter;
BMLoop *l;
int total = 0;
zero_v3(avg);
BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
const BMVert *adj_v[2] = {l->prev->v, l->next->v};
for (int i = 0; i < ARRAY_SIZE(adj_v); i++) {
const BMVert *v_other = adj_v[i];
if (vfcount != 2 || BM_vert_face_count_at_most(v_other, 2) <= 2) {
add_v3_v3(avg, v_other->co);
total++;
}
}
}
if (total > 0) {
mul_v3_fl(avg, 1.0f / total);
return;
}
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) {
add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index));
total++;
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
copy_v3_v3(avg, v->co);
if (total > 0) {
mul_v3_v3fl(result, avg, 1.0f / total);
}
else {
copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
}
}
/* For bmesh: Average surrounding verts based on an orthogonality measure.
@ -244,125 +195,9 @@ void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], int index
}
}
static void do_smooth_brush_mesh_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
Sculpt *sd = data->sd;
const Brush *brush = data->brush;
const bool smooth_mask = data->smooth_mask;
float bstrength = data->strength;
PBVHVertexIter vd;
CLAMP(bstrength, 0.0f, 1.0f);
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
const int thread_id = BLI_task_parallel_thread_id(tls);
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,
smooth_mask ? 0.0f : (vd.mask ? *vd.mask : 0.0f),
vd.index,
thread_id);
if (smooth_mask) {
float val = SCULPT_neighbor_mask_average(ss, vd.vert_indices[vd.i]) - *vd.mask;
val *= fade * bstrength;
*vd.mask += val;
CLAMP(*vd.mask, 0.0f, 1.0f);
}
else {
float avg[3], val[3];
SCULPT_neighbor_average(ss, avg, vd.vert_indices[vd.i]);
sub_v3_v3v3(val, avg, vd.co);
madd_v3_v3v3fl(val, vd.co, val, fade);
SCULPT_clip(sd, ss, vd.co, val);
}
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
}
BKE_pbvh_vertex_iter_end;
}
static void do_smooth_brush_bmesh_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
Sculpt *sd = data->sd;
const Brush *brush = data->brush;
const bool smooth_mask = data->smooth_mask;
float bstrength = data->strength;
PBVHVertexIter vd;
CLAMP(bstrength, 0.0f, 1.0f);
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
const int thread_id = BLI_task_parallel_thread_id(tls);
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,
smooth_mask ? 0.0f : *vd.mask,
vd.index,
thread_id);
if (smooth_mask) {
float val = SCULPT_neighbor_mask_average(ss, vd.index) - *vd.mask;
val *= fade * bstrength;
*vd.mask += val;
CLAMP(*vd.mask, 0.0f, 1.0f);
}
else {
float avg[3], val[3];
SCULPT_bmesh_neighbor_average(avg, vd.bm_vert);
sub_v3_v3v3(val, avg, vd.co);
madd_v3_v3v3fl(val, vd.co, val, fade);
SCULPT_clip(sd, ss, vd.co, val);
}
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
}
BKE_pbvh_vertex_iter_end;
}
static void do_smooth_brush_multires_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
@ -438,6 +273,8 @@ void SCULPT_smooth(Sculpt *sd,
return;
}
SCULPT_boundary_info_ensure(ob);
for (iteration = 0; iteration <= count; iteration++) {
const float strength = (iteration != count) ? 1.0f : last;
@ -452,18 +289,7 @@ void SCULPT_smooth(Sculpt *sd,
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
switch (type) {
case PBVH_GRIDS:
BLI_task_parallel_range(0, totnode, &data, do_smooth_brush_multires_task_cb_ex, &settings);
break;
case PBVH_FACES:
BLI_task_parallel_range(0, totnode, &data, do_smooth_brush_mesh_task_cb_ex, &settings);
break;
case PBVH_BMESH:
BLI_task_parallel_range(0, totnode, &data, do_smooth_brush_bmesh_task_cb_ex, &settings);
break;
}
BLI_task_parallel_range(0, totnode, &data, do_smooth_brush_task_cb_ex, &settings);
}
}