Sculpt: Initial data oriented refactor for crease/blob brushes

Part of #118145.
I added a scene argument to the brush symmetry operation functions.
Besides that, the changes here are just like the other refactors.

Pull Request: https://projects.blender.org/blender/blender/pulls/123231
This commit is contained in:
Hans Goudey
2024-06-20 00:15:47 +02:00
committed by Hans Goudey
parent ffad89f028
commit 7997d62598
7 changed files with 382 additions and 131 deletions

View File

@@ -116,6 +116,7 @@ set(SRC
sculpt_intern.hh
brushes/clay_strips.cc
brushes/crease.cc
brushes/draw_vector_displacement.cc
brushes/draw.cc
brushes/fill.cc

View File

@@ -0,0 +1,341 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "editors/sculpt_paint/brushes/types.hh"
#include "DNA_brush_types.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_brush.hh"
#include "BKE_key.hh"
#include "BKE_mesh.hh"
#include "BKE_paint.hh"
#include "BKE_pbvh.hh"
#include "BKE_subdiv_ccg.hh"
#include "BLI_array.hh"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.hh"
#include "BLI_task.h"
#include "BLI_task.hh"
#include "editors/sculpt_paint/mesh_brush_common.hh"
#include "editors/sculpt_paint/sculpt_intern.hh"
namespace blender::ed::sculpt_paint {
inline namespace crease_cc {
struct LocalData {
Vector<float> factors;
Vector<float> distances;
Vector<float3> translations;
};
BLI_NOINLINE static void translations_from_position(const Span<float3> positions_eval,
const Span<int> verts,
const float3 &location,
const MutableSpan<float3> translations)
{
for (const int i : verts.index_range()) {
translations[i] = location - positions_eval[verts[i]];
}
}
BLI_NOINLINE static void project_translations(const MutableSpan<float3> translations,
const float3 &plane)
{
/* Equivalent to #project_plane_v3_v3v3. */
const float len_sq = math::length_squared(plane);
if (len_sq < std::numeric_limits<float>::epsilon()) {
return;
}
const float dot_factor = -math::rcp(len_sq);
for (const int i : translations.index_range()) {
translations[i] += plane * math::dot(translations[i], plane) * dot_factor;
}
}
BLI_NOINLINE static void add_offset_to_translations(const MutableSpan<float3> translations,
const Span<float> factors,
const float3 &offset)
{
for (const int i : translations.index_range()) {
translations[i] += offset * factors[i];
}
}
static void calc_faces(const Sculpt &sd,
const Brush &brush,
const float3 &offset,
const float strength,
const Span<float3> positions_eval,
const Span<float3> vert_normals,
const PBVHNode &node,
Object &object,
LocalData &tls,
const MutableSpan<float3> positions_orig)
{
SculptSession &ss = *object.sculpt;
const StrokeCache &cache = *ss.cache;
Mesh &mesh = *static_cast<Mesh *>(object.data);
const Span<int> verts = bke::pbvh::node_unique_verts(node);
tls.factors.reinitialize(verts.size());
const MutableSpan<float> factors = tls.factors;
fill_factor_from_hide_and_mask(mesh, verts, factors);
if (brush.flag & BRUSH_FRONTFACE) {
calc_front_face(cache.view_normal, vert_normals, verts, factors);
}
tls.distances.reinitialize(verts.size());
const MutableSpan<float> distances = tls.distances;
calc_distance_falloff(
ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors);
calc_brush_strength_factors(cache, brush, distances, factors);
if (cache.automasking) {
auto_mask::calc_vert_factors(object, *cache.automasking, node, verts, factors);
}
calc_brush_texture_factors(ss, brush, positions_eval, verts, factors);
tls.translations.reinitialize(verts.size());
const MutableSpan<float3> translations = tls.translations;
translations_from_position(positions_eval, verts, cache.location, translations);
if (brush.falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
project_translations(translations, cache.view_normal);
}
scale_translations(translations, factors);
scale_translations(translations, strength);
/* The vertices are pinched towards a line instead of a single point. Without this we get a
* 'flat' surface surrounding the pinch. */
project_translations(translations, cache.sculpt_normal_symm);
add_offset_to_translations(translations, factors, offset);
write_translations(sd, object, positions_eval, verts, translations, positions_orig);
}
static void calc_grids(
Object &object, const Brush &brush, const float3 &offset, const float strength, PBVHNode &node)
{
SculptSession &ss = *object.sculpt;
const StrokeCache &cache = *ss.cache;
const float3 &location = cache.location;
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(
object, ss.cache->automasking.get(), node);
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
const CCGKey key = *BKE_pbvh_get_grid_key(*ss.pbvh);
const Span<CCGElem *> grids = subdiv_ccg.grids;
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
/* TODO: Remove usage of proxies. */
const MutableSpan<float3> proxy = BKE_pbvh_node_add_proxy(*ss.pbvh, node).co;
int i = 0;
for (const int grid : bke::pbvh::node_grid_indices(node)) {
const int grid_verts_start = grid * key.grid_area;
CCGElem *elem = grids[grid];
for (const int j : IndexRange(key.grid_area)) {
if (!grid_hidden.is_empty() && grid_hidden[grid][j]) {
i++;
continue;
}
const float3 &co = CCG_elem_offset_co(key, elem, j);
if (!sculpt_brush_test_sq_fn(test, co)) {
i++;
continue;
}
float3 translation = location - co;
if (brush.falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
project_translations({&translation, 1}, cache.view_normal);
}
auto_mask::node_update(automask_data, i);
const float fade = SCULPT_brush_strength_factor(
ss,
brush,
co,
math::sqrt(test.dist),
CCG_elem_offset_no(key, elem, j),
nullptr,
key.has_mask ? CCG_elem_offset_mask(key, elem, j) : 0.0f,
BKE_pbvh_make_vref(grid_verts_start + j),
thread_id,
&automask_data);
translation *= fade;
translation *= strength;
/* The vertices are pinched towards a line instead of a single point. Without this we get a
* 'flat' surface surrounding the pinch. */
project_translations({&translation, 1}, cache.sculpt_normal_symm);
translation += offset * fade;
proxy[i] = translation;
i++;
}
}
}
static void calc_bmesh(
Object &object, const Brush &brush, const float3 &offset, const float strength, PBVHNode &node)
{
SculptSession &ss = *object.sculpt;
const StrokeCache &cache = *ss.cache;
const float3 &location = cache.location;
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(
object, ss.cache->automasking.get(), node);
const int mask_offset = CustomData_get_offset_named(
&ss.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
/* TODO: Remove usage of proxies. */
const MutableSpan<float3> proxy = BKE_pbvh_node_add_proxy(*ss.pbvh, node).co;
int i = 0;
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(&node)) {
if (BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
i++;
continue;
}
if (!sculpt_brush_test_sq_fn(test, vert->co)) {
i++;
continue;
}
float3 translation = location - float3(vert->co);
if (brush.falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
project_translations({&translation, 1}, cache.view_normal);
}
auto_mask::node_update(automask_data, i);
const float mask = mask_offset == -1 ? 0.0f : BM_ELEM_CD_GET_FLOAT(vert, mask_offset);
const float fade = SCULPT_brush_strength_factor(ss,
brush,
vert->co,
math::sqrt(test.dist),
vert->no,
nullptr,
mask,
BKE_pbvh_make_vref(intptr_t(vert)),
thread_id,
&automask_data);
translation *= fade;
translation *= strength;
/* The vertices are pinched towards a line instead of a single point. Without this we get a
* 'flat' surface surrounding the pinch. */
project_translations({&translation, 1}, cache.sculpt_normal_symm);
translation += offset * fade;
proxy[i] = translation;
i++;
}
}
static void do_crease_or_blob_brush(const Scene &scene,
const Sculpt &sd,
const bool invert_strength,
Object &object,
Span<PBVHNode *> nodes)
{
const SculptSession &ss = *object.sculpt;
const StrokeCache &cache = *ss.cache;
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
/* Offset with as much as possible factored in already. */
const float3 offset = cache.sculpt_normal_symm * cache.scale * cache.radius * cache.bstrength;
/* We divide out the squared alpha and multiply by the squared crease
* to give us the pinch strength. */
float crease_correction = brush.crease_pinch_factor * brush.crease_pinch_factor;
float brush_alpha = BKE_brush_alpha_get(&scene, &brush);
if (brush_alpha > 0.0f) {
crease_correction /= brush_alpha * brush_alpha;
}
/* We always want crease to pinch or blob to relax even when draw is negative. */
const float strength = std::abs(cache.bstrength) * crease_correction *
(invert_strength ? -1.0f : 1.0f);
switch (BKE_pbvh_type(*object.sculpt->pbvh)) {
case PBVH_FACES: {
threading::EnumerableThreadSpecific<LocalData> all_tls;
Mesh &mesh = *static_cast<Mesh *>(object.data);
const PBVH &pbvh = *ss.pbvh;
const Span<float3> positions_eval = BKE_pbvh_get_vert_positions(pbvh);
const Span<float3> vert_normals = BKE_pbvh_get_vert_normals(pbvh);
MutableSpan<float3> positions_orig = mesh.vert_positions_for_write();
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
LocalData &tls = all_tls.local();
for (const int i : range) {
calc_faces(sd,
brush,
offset,
strength,
positions_eval,
vert_normals,
*nodes[i],
object,
tls,
positions_orig);
BKE_pbvh_node_mark_positions_update(nodes[i]);
}
});
break;
}
case PBVH_GRIDS:
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
calc_grids(object, brush, offset, strength, *nodes[i]);
}
});
break;
case PBVH_BMESH:
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
calc_bmesh(object, brush, offset, strength, *nodes[i]);
}
});
break;
}
}
} // namespace crease_cc
void do_crease_brush(const Scene &scene, const Sculpt &sd, Object &object, Span<PBVHNode *> nodes)
{
do_crease_or_blob_brush(scene, sd, false, object, nodes);
}
void do_blob_brush(const Scene &scene, const Sculpt &sd, Object &object, Span<PBVHNode *> nodes)
{
do_crease_or_blob_brush(scene, sd, true, object, nodes);
}
} // namespace blender::ed::sculpt_paint

View File

@@ -6,6 +6,7 @@
#include "BLI_span.hh"
struct Scene;
struct Sculpt;
struct Object;
struct PBVHNode;
@@ -13,6 +14,8 @@ struct PBVHNode;
namespace blender::ed::sculpt_paint {
void do_clay_strips_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
void do_crease_brush(const Scene &scene, const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
void do_blob_brush(const Scene &scene, const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
void do_displacement_eraser_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
/** A simple normal-direction displacement. */
void do_draw_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);

View File

@@ -45,6 +45,7 @@ struct Cache;
};
void scale_translations(MutableSpan<float3> translations, Span<float> factors);
void scale_translations(MutableSpan<float3> translations, float factor);
void scale_factors(MutableSpan<float> factors, float strength);
/**

View File

@@ -3485,7 +3485,8 @@ namespace blender::ed::sculpt_paint {
/* NOTE: we do the topology update before any brush actions to avoid
* issues with the proxies. The size of the proxy can't change, so
* topology must be updated first. */
static void sculpt_topology_update(const Sculpt &sd,
static void sculpt_topology_update(const Scene & /*scene*/,
const Sculpt &sd,
Object &ob,
const Brush &brush,
UnifiedPaintSettings & /*ups*/,
@@ -3578,7 +3579,8 @@ static void push_undo_nodes(Object &ob, const Brush &brush, PBVHNode *node)
}
}
static void do_brush_action(const Sculpt &sd,
static void do_brush_action(const Scene &scene,
const Sculpt &sd,
Object &ob,
const Brush &brush,
UnifiedPaintSettings &ups,
@@ -3747,10 +3749,10 @@ static void do_brush_action(const Sculpt &sd,
}
break;
case SCULPT_TOOL_CREASE:
SCULPT_do_crease_brush(sd, ob, nodes);
do_crease_brush(scene, sd, ob, nodes);
break;
case SCULPT_TOOL_BLOB:
SCULPT_do_crease_brush(sd, ob, nodes);
do_blob_brush(scene, sd, ob, nodes);
break;
case SCULPT_TOOL_PINCH:
SCULPT_do_pinch_brush(sd, ob, nodes);
@@ -4134,13 +4136,15 @@ void SCULPT_cache_calc_brushdata_symm(blender::ed::sculpt_paint::StrokeCache &ca
namespace blender::ed::sculpt_paint {
using BrushActionFunc = void (*)(const Sculpt &sd,
using BrushActionFunc = void (*)(const Scene &scene,
const Sculpt &sd,
Object &ob,
const Brush &brush,
UnifiedPaintSettings &ups,
PaintModeSettings &paint_mode_settings);
static void do_tiled(const Sculpt &sd,
static void do_tiled(const Scene &scene,
const Sculpt &sd,
Object &ob,
const Brush &brush,
UnifiedPaintSettings &ups,
@@ -4179,7 +4183,7 @@ static void do_tiled(const Sculpt &sd,
/* First do the "un-tiled" position to initialize the stroke for this location. */
cache->tile_pass = 0;
action(sd, ob, brush, ups, paint_mode_settings);
action(scene, sd, ob, brush, ups, paint_mode_settings);
/* Now do it for all the tiles. */
copy_v3_v3_int(cur, start);
@@ -4198,13 +4202,14 @@ static void do_tiled(const Sculpt &sd,
cache->plane_offset[dim] = cur[dim] * step[dim];
cache->initial_location[dim] = cur[dim] * step[dim] + original_initial_location[dim];
}
action(sd, ob, brush, ups, paint_mode_settings);
action(scene, sd, ob, brush, ups, paint_mode_settings);
}
}
}
}
static void do_radial_symmetry(const Sculpt &sd,
static void do_radial_symmetry(const Scene &scene,
const Sculpt &sd,
Object &ob,
const Brush &brush,
UnifiedPaintSettings &ups,
@@ -4220,7 +4225,7 @@ static void do_radial_symmetry(const Sculpt &sd,
const float angle = 2.0f * M_PI * i / sd.radial_symm[axis - 'X'];
ss.cache->radial_symmetry_pass = i;
SCULPT_cache_calc_brushdata_symm(*ss.cache, symm, axis, angle);
do_tiled(sd, ob, brush, ups, paint_mode_settings, action);
do_tiled(scene, sd, ob, brush, ups, paint_mode_settings, action);
}
}
@@ -4239,7 +4244,8 @@ static void sculpt_fix_noise_tear(const Sculpt &sd, Object &ob)
}
}
static void do_symmetrical_brush_actions(const Sculpt &sd,
static void do_symmetrical_brush_actions(const Scene &scene,
const Sculpt &sd,
Object &ob,
const BrushActionFunc action,
UnifiedPaintSettings &ups,
@@ -4266,11 +4272,11 @@ static void do_symmetrical_brush_actions(const Sculpt &sd,
cache.radial_symmetry_pass = 0;
SCULPT_cache_calc_brushdata_symm(cache, symm, 0, 0);
do_tiled(sd, ob, brush, ups, paint_mode_settings, action);
do_tiled(scene, sd, ob, brush, ups, paint_mode_settings, action);
do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, symm, 'X', feather);
do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, symm, 'Y', feather);
do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, symm, 'Z', feather);
do_radial_symmetry(scene, sd, ob, brush, ups, paint_mode_settings, action, symm, 'X', feather);
do_radial_symmetry(scene, sd, ob, brush, ups, paint_mode_settings, action, symm, 'Y', feather);
do_radial_symmetry(scene, sd, ob, brush, ups, paint_mode_settings, action, symm, 'Z', feather);
}
}
@@ -5800,6 +5806,7 @@ static void sculpt_stroke_update_step(bContext *C,
PointerRNA *itemptr)
{
UnifiedPaintSettings &ups = CTX_data_tool_settings(C)->unified_paint_settings;
const Scene &scene = *CTX_data_scene(C);
Sculpt &sd = *CTX_data_tool_settings(C)->sculpt;
Object &ob = *CTX_data_active_object(C);
SculptSession &ss = *ob.sculpt;
@@ -5828,10 +5835,11 @@ static void sculpt_stroke_update_step(bContext *C,
}
if (dyntopo::stroke_is_dyntopo(ss, brush)) {
do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups, tool_settings.paint_mode);
do_symmetrical_brush_actions(
scene, sd, ob, sculpt_topology_update, ups, tool_settings.paint_mode);
}
do_symmetrical_brush_actions(sd, ob, do_brush_action, ups, tool_settings.paint_mode);
do_symmetrical_brush_actions(scene, sd, ob, do_brush_action, ups, tool_settings.paint_mode);
sculpt_combine_proxies(sd, ob);
/* Hack to fix noise texture tearing mesh. */
@@ -5851,8 +5859,10 @@ static void sculpt_stroke_update_step(bContext *C,
*/
if (!(ELEM(brush.sculpt_tool,
SCULPT_TOOL_DRAW,
SCULPT_TOOL_CLAY_STRIPS,
SCULPT_TOOL_SCRAPE,
SCULPT_TOOL_BLOB,
SCULPT_TOOL_CREASE,
SCULPT_TOOL_CLAY_STRIPS,
SCULPT_TOOL_FILL) &&
BKE_pbvh_type(*ss.pbvh) == PBVH_FACES))
{
@@ -6543,7 +6553,7 @@ void calc_front_face(const float3 &view_normal,
for (const int i : verts.index_range()) {
const float dot = math::dot(view_normal, vert_normals[verts[i]]);
factors[i] *= dot > 0.0f ? dot : 0.0f;
factors[i] *= std::max(dot, 0.0f);
}
}
@@ -6791,6 +6801,13 @@ void scale_translations(const MutableSpan<float3> translations, const Span<float
}
}
void scale_translations(const MutableSpan<float3> translations, const float factor)
{
for (const int i : translations.index_range()) {
translations[i] *= factor;
}
}
void scale_factors(const MutableSpan<float> factors, const float strength)
{
if (strength == 1.0f) {

View File

@@ -829,117 +829,6 @@ void SCULPT_do_layer_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sculpt Crease & Blob Brush
* \{ */
/**
* Used for 'SCULPT_TOOL_CREASE' and 'SCULPT_TOOL_BLOB'
*/
static void do_crease_brush_task(Object &ob,
const Brush &brush,
SculptProjectVector *spvc,
const float flippedbstrength,
const float *offset,
PBVHNode *node)
{
using namespace blender::ed::sculpt_paint;
SculptSession &ss = *ob.sculpt;
PBVHVertexIter vd;
const MutableSpan<float3> proxy = BKE_pbvh_node_add_proxy(*ss.pbvh, *node).co;
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;
}
/* Offset vertex. */
auto_mask::node_update(automask_data, vd);
const float fade = SCULPT_brush_strength_factor(ss,
brush,
vd.co,
sqrtf(test.dist),
vd.no,
vd.fno,
vd.mask,
vd.vertex,
thread_id,
&automask_data);
float val1[3];
float val2[3];
/* First we pinch. */
sub_v3_v3v3(val1, test.location, vd.co);
if (brush.falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
project_plane_v3_v3v3(val1, val1, ss.cache->view_normal);
}
mul_v3_fl(val1, fade * flippedbstrength);
sculpt_project_v3(spvc, val1, val1);
/* Then we draw. */
mul_v3_v3fl(val2, offset, fade);
add_v3_v3v3(proxy[vd.i], val1, val2);
}
BKE_pbvh_vertex_iter_end;
}
void SCULPT_do_crease_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
{
using namespace blender;
SculptSession &ss = *ob.sculpt;
const Scene *scene = ss.cache->vc->scene;
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
float offset[3];
float bstrength = ss.cache->bstrength;
float flippedbstrength, crease_correction;
float brush_alpha;
SculptProjectVector spvc;
/* Offset with as much as possible factored in already. */
mul_v3_v3fl(offset, ss.cache->sculpt_normal_symm, ss.cache->radius);
mul_v3_v3(offset, ss.cache->scale);
mul_v3_fl(offset, bstrength);
/* We divide out the squared alpha and multiply by the squared crease
* to give us the pinch strength. */
crease_correction = brush.crease_pinch_factor * brush.crease_pinch_factor;
brush_alpha = BKE_brush_alpha_get(scene, &brush);
if (brush_alpha > 0.0f) {
crease_correction /= brush_alpha * brush_alpha;
}
/* We always want crease to pinch or blob to relax even when draw is negative. */
flippedbstrength = (bstrength < 0.0f) ? -crease_correction * bstrength :
crease_correction * bstrength;
if (brush.sculpt_tool == SCULPT_TOOL_BLOB) {
flippedbstrength *= -1.0f;
}
/* Use surface normal for 'spvc', so the vertices are pinched towards a line instead of a single
* point. Without this we get a 'flat' surface surrounding the pinch. */
sculpt_project_v3_cache_init(&spvc, ss.cache->sculpt_normal_symm);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
do_crease_brush_task(ob, brush, &spvc, flippedbstrength, offset, nodes[i]);
}
});
}
static void do_pinch_brush_task(Object &ob,
const Brush &brush,
const float (*stroke_xz)[3],

View File

@@ -2049,7 +2049,6 @@ void SCULPT_do_snake_hook_brush(const Sculpt &sd, Object &ob, blender::Span<PBVH
void SCULPT_do_thumb_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_rotate_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_layer_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_crease_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_pinch_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_grab_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_elastic_deform_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);