587 lines
19 KiB
C++
587 lines
19 KiB
C++
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup edsculpt
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_task.h"
|
|
|
|
#include "DNA_brush_types.h"
|
|
|
|
#include "BKE_paint.hh"
|
|
#include "BKE_pbvh_api.hh"
|
|
|
|
#include "sculpt_intern.hh"
|
|
|
|
#include "bmesh.hh"
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
|
|
namespace blender::ed::sculpt_paint::smooth {
|
|
|
|
void neighbor_coords_average_interior(SculptSession *ss, float result[3], PBVHVertRef vertex)
|
|
{
|
|
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, vertex);
|
|
|
|
SculptVertexNeighborIter ni;
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
|
|
neighbor_count++;
|
|
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++;
|
|
}
|
|
}
|
|
else {
|
|
/* Interior vertices use all neighbors. */
|
|
add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex));
|
|
total++;
|
|
}
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
|
|
/* Do not modify corner vertices. */
|
|
if (neighbor_count <= 2 && is_boundary) {
|
|
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, vertex));
|
|
return;
|
|
}
|
|
|
|
mul_v3_v3fl(result, avg, 1.0f / total);
|
|
}
|
|
|
|
void bmesh_four_neighbor_average(float avg[3], float direction[3], BMVert *v)
|
|
{
|
|
|
|
float avg_co[3] = {0.0f, 0.0f, 0.0f};
|
|
float tot_co = 0.0f;
|
|
|
|
BMIter eiter;
|
|
BMEdge *e;
|
|
|
|
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
|
|
if (BM_edge_is_boundary(e)) {
|
|
copy_v3_v3(avg, v->co);
|
|
return;
|
|
}
|
|
BMVert *v_other = (e->v1 == v) ? e->v2 : e->v1;
|
|
float vec[3];
|
|
sub_v3_v3v3(vec, v_other->co, v->co);
|
|
madd_v3_v3fl(vec, v->no, -dot_v3v3(vec, v->no));
|
|
normalize_v3(vec);
|
|
|
|
/* fac is a measure of how orthogonal or parallel the edge is
|
|
* relative to the direction. */
|
|
float fac = dot_v3v3(vec, direction);
|
|
fac = fac * fac - 0.5f;
|
|
fac *= fac;
|
|
madd_v3_v3fl(avg_co, v_other->co, fac);
|
|
tot_co += fac;
|
|
}
|
|
|
|
/* In case vert has no Edge s. */
|
|
if (tot_co > 0.0f) {
|
|
mul_v3_v3fl(avg, avg_co, 1.0f / tot_co);
|
|
|
|
/* Preserve volume. */
|
|
float vec[3];
|
|
sub_v3_v3(avg, v->co);
|
|
mul_v3_v3fl(vec, v->no, dot_v3v3(avg, v->no));
|
|
sub_v3_v3(avg, vec);
|
|
add_v3_v3(avg, v->co);
|
|
}
|
|
else {
|
|
zero_v3(avg);
|
|
}
|
|
}
|
|
|
|
/* Generic functions for laplacian smoothing. These functions do not take boundary vertices into
|
|
* account. */
|
|
|
|
void neighbor_coords_average(SculptSession *ss, float result[3], PBVHVertRef vertex)
|
|
{
|
|
float avg[3] = {0.0f, 0.0f, 0.0f};
|
|
int total = 0;
|
|
|
|
SculptVertexNeighborIter ni;
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
|
|
add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.vertex));
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
|
|
if (total > 0) {
|
|
mul_v3_v3fl(result, avg, 1.0f / total);
|
|
}
|
|
else {
|
|
copy_v3_v3(result, SCULPT_vertex_co_get(ss, vertex));
|
|
}
|
|
}
|
|
|
|
float neighbor_mask_average(SculptSession *ss,
|
|
const SculptMaskWriteInfo write_info,
|
|
PBVHVertRef vertex)
|
|
{
|
|
float avg = 0.0f;
|
|
int total = 0;
|
|
SculptVertexNeighborIter ni;
|
|
switch (BKE_pbvh_type(ss->pbvh)) {
|
|
case PBVH_FACES:
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
|
|
avg += write_info.layer[ni.vertex.i];
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
break;
|
|
case PBVH_GRIDS:
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
|
|
avg += SCULPT_mask_get_at_grids_vert_index(
|
|
*ss->subdiv_ccg, *BKE_pbvh_get_grid_key(ss->pbvh), ni.vertex.i);
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
break;
|
|
case PBVH_BMESH:
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
|
|
BMVert *vert = reinterpret_cast<BMVert *>(ni.vertex.i);
|
|
avg += BM_ELEM_CD_GET_FLOAT(vert, write_info.bm_offset);
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
break;
|
|
}
|
|
BLI_assert(total > 0);
|
|
return avg / total;
|
|
}
|
|
|
|
void neighbor_color_average(SculptSession *ss, float result[4], PBVHVertRef vertex)
|
|
{
|
|
float avg[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
int total = 0;
|
|
|
|
SculptVertexNeighborIter ni;
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
|
|
float tmp[4] = {0};
|
|
|
|
SCULPT_vertex_color_get(ss, ni.vertex, tmp);
|
|
|
|
add_v4_v4(avg, tmp);
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
|
|
if (total > 0) {
|
|
mul_v4_v4fl(result, avg, 1.0f / total);
|
|
}
|
|
else {
|
|
SCULPT_vertex_color_get(ss, vertex, result);
|
|
}
|
|
}
|
|
|
|
static void do_enhance_details_brush_task(Object *ob,
|
|
Sculpt *sd,
|
|
const Brush *brush,
|
|
PBVHNode *node)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
PBVHVertexIter vd;
|
|
|
|
float bstrength = ss->cache->bstrength;
|
|
CLAMP(bstrength, -1.0f, 1.0f);
|
|
|
|
SculptBrushTest test;
|
|
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
|
ss, &test, brush->falloff_shape);
|
|
|
|
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
|
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
|
*ob, ss->cache->automasking.get(), *node);
|
|
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
|
|
auto_mask::node_update(automask_data, vd);
|
|
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask,
|
|
vd.vertex,
|
|
thread_id,
|
|
&automask_data);
|
|
|
|
float disp[3];
|
|
madd_v3_v3v3fl(disp, vd.co, ss->cache->detail_directions[vd.index], fade);
|
|
SCULPT_clip(sd, ss, vd.co, disp);
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
}
|
|
|
|
static void enhance_details_brush(Sculpt *sd, Object *ob, Span<PBVHNode *> nodes)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
SCULPT_vertex_random_access_ensure(ss);
|
|
SCULPT_boundary_info_ensure(ob);
|
|
|
|
if (SCULPT_stroke_is_first_brush_step(ss->cache)) {
|
|
const int totvert = SCULPT_vertex_count_get(ss);
|
|
ss->cache->detail_directions = static_cast<float(*)[3]>(
|
|
MEM_malloc_arrayN(totvert, sizeof(float[3]), "details directions"));
|
|
|
|
for (int i = 0; i < totvert; i++) {
|
|
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
|
|
|
|
float avg[3];
|
|
neighbor_coords_average(ss, avg, vertex);
|
|
sub_v3_v3v3(ss->cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex));
|
|
}
|
|
}
|
|
|
|
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
do_enhance_details_brush_task(ob, sd, brush, nodes[i]);
|
|
}
|
|
});
|
|
}
|
|
|
|
static void smooth_mask_node(Object *ob,
|
|
const Brush *brush,
|
|
const SculptMaskWriteInfo mask_write,
|
|
float bstrength,
|
|
PBVHNode *node)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
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, brush->falloff_shape);
|
|
|
|
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
|
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
|
*ob, ss->cache->automasking.get(), *node);
|
|
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
|
|
auto_mask::node_update(automask_data, vd);
|
|
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
0.0f,
|
|
vd.vertex,
|
|
thread_id,
|
|
&automask_data);
|
|
float val = neighbor_mask_average(ss, mask_write, vd.vertex) - vd.mask;
|
|
val *= fade * bstrength;
|
|
float new_mask = vd.mask + val;
|
|
CLAMP(new_mask, 0.0f, 1.0f);
|
|
|
|
SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, new_mask, vd);
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
}
|
|
|
|
void do_smooth_mask_brush(Sculpt *sd, Object *ob, Span<PBVHNode *> nodes, float bstrength)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
const Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
const int max_iterations = 4;
|
|
const float fract = 1.0f / max_iterations;
|
|
|
|
CLAMP(bstrength, 0.0f, 1.0f);
|
|
|
|
const int count = int(bstrength * max_iterations);
|
|
const float last = max_iterations * (bstrength - count * fract);
|
|
|
|
SCULPT_vertex_random_access_ensure(ss);
|
|
SCULPT_boundary_info_ensure(ob);
|
|
|
|
SculptMaskWriteInfo mask_write = SCULPT_mask_get_for_write(ss);
|
|
|
|
for (const int iteration : IndexRange(count + 1)) {
|
|
const float strength = (iteration != count) ? 1.0f : last;
|
|
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
smooth_mask_node(ob, brush, mask_write, strength, nodes[i]);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
static void smooth_position_node(
|
|
Object *ob, Sculpt *sd, const Brush *brush, float bstrength, PBVHNode *node)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
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, brush->falloff_shape);
|
|
|
|
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
|
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
|
*ob, ss->cache->automasking.get(), *node);
|
|
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
|
|
auto_mask::node_update(automask_data, vd);
|
|
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask,
|
|
vd.vertex,
|
|
thread_id,
|
|
&automask_data);
|
|
|
|
float avg[3], val[3];
|
|
neighbor_coords_average_interior(ss, avg, vd.vertex);
|
|
sub_v3_v3v3(val, avg, vd.co);
|
|
madd_v3_v3v3fl(val, vd.co, val, fade);
|
|
SCULPT_clip(sd, ss, vd.co, val);
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
}
|
|
|
|
void do_smooth_brush(Sculpt *sd, Object *ob, Span<PBVHNode *> nodes, float bstrength)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
const Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
const int max_iterations = 4;
|
|
const float fract = 1.0f / max_iterations;
|
|
|
|
CLAMP(bstrength, 0.0f, 1.0f);
|
|
|
|
const int count = int(bstrength * max_iterations);
|
|
const float last = max_iterations * (bstrength - count * fract);
|
|
|
|
SCULPT_vertex_random_access_ensure(ss);
|
|
SCULPT_boundary_info_ensure(ob);
|
|
|
|
for (const int iteration : IndexRange(count + 1)) {
|
|
const float strength = (iteration != count) ? 1.0f : last;
|
|
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
smooth_position_node(ob, sd, brush, strength, nodes[i]);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void do_smooth_brush(Sculpt *sd, Object *ob, Span<PBVHNode *> nodes)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
/* NOTE: The enhance brush needs to initialize its state on the first brush step. The stroke
|
|
* strength can become 0 during the stroke, but it can not change sign (the sign is determined
|
|
* in the beginning of the stroke. So here it is important to not switch to enhance brush in the
|
|
* middle of the stroke. */
|
|
if (ss->cache->bstrength < 0.0f) {
|
|
/* Invert mode, intensify details. */
|
|
enhance_details_brush(sd, ob, nodes);
|
|
}
|
|
else {
|
|
/* Regular mode, smooth. */
|
|
do_smooth_brush(sd, ob, nodes, ss->cache->bstrength);
|
|
}
|
|
}
|
|
|
|
/* HC Smooth Algorithm. */
|
|
/* From: Improved Laplacian Smoothing of Noisy Surface Meshes */
|
|
|
|
void surface_smooth_laplacian_step(SculptSession *ss,
|
|
float *disp,
|
|
const float co[3],
|
|
float (*laplacian_disp)[3],
|
|
const PBVHVertRef vertex,
|
|
const float origco[3],
|
|
const float alpha)
|
|
{
|
|
float laplacian_smooth_co[3];
|
|
float weigthed_o[3], weigthed_q[3], d[3];
|
|
int v_index = BKE_pbvh_vertex_to_index(ss->pbvh, vertex);
|
|
|
|
neighbor_coords_average(ss, laplacian_smooth_co, vertex);
|
|
|
|
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);
|
|
}
|
|
|
|
void surface_smooth_displace_step(SculptSession *ss,
|
|
float *co,
|
|
float (*laplacian_disp)[3],
|
|
const PBVHVertRef vertex,
|
|
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, vertex, ni) {
|
|
add_v3_v3(b_avg, laplacian_disp[ni.index]);
|
|
total++;
|
|
}
|
|
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
if (total > 0) {
|
|
int v_index = BKE_pbvh_vertex_to_index(ss->pbvh, vertex);
|
|
|
|
mul_v3_v3fl(b_current_vertex, b_avg, (1.0f - beta) / 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(Object *ob, const Brush *brush, PBVHNode *node)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
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, brush->falloff_shape);
|
|
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
|
|
|
SCULPT_orig_vert_data_init(&orig_data, ob, node, undo::Type::Position);
|
|
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
|
*ob, ss->cache->automasking.get(), *node);
|
|
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
|
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
|
|
auto_mask::node_update(automask_data, vd);
|
|
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask,
|
|
vd.vertex,
|
|
thread_id,
|
|
&automask_data);
|
|
|
|
float disp[3];
|
|
surface_smooth_laplacian_step(
|
|
ss, disp, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.vertex, 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(Object *ob, const Brush *brush, PBVHNode *node)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
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, brush->falloff_shape);
|
|
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
|
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
|
*ob, ss->cache->automasking.get(), *node);
|
|
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
|
|
auto_mask::node_update(automask_data, vd);
|
|
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask,
|
|
vd.vertex,
|
|
thread_id,
|
|
&automask_data);
|
|
surface_smooth_displace_step(
|
|
ss, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.vertex, beta, fade);
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
}
|
|
|
|
void do_surface_smooth_brush(Sculpt *sd, Object *ob, Span<PBVHNode *> nodes)
|
|
{
|
|
Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
for (int i = 0; i < brush->surface_smooth_iterations; i++) {
|
|
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
do_surface_smooth_brush_laplacian_task(ob, brush, nodes[i]);
|
|
}
|
|
});
|
|
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
do_surface_smooth_brush_displace_task(ob, brush, nodes[i]);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint::smooth
|