Sculpt: Initial data oriented refactor for grab brush

Part of #118145.
This introduces a few small API functions for retrieving spans of
original positions and normals from the undo system.

Pull Request: https://projects.blender.org/blender/blender/pulls/123447
This commit is contained in:
Hans Goudey 2024-07-01 22:32:01 +02:00 committed by Hans Goudey
parent 1d10336afe
commit 7bc188a760
9 changed files with 359 additions and 100 deletions

@ -121,6 +121,7 @@ set(SRC
brushes/draw.cc
brushes/fill.cc
brushes/flatten.cc
brushes/grab.cc
brushes/inflate.cc
brushes/mask.cc
brushes/multiplane_scrape.cc

@ -36,15 +36,6 @@ struct LocalData {
Vector<float3> translations;
};
BLI_NOINLINE static void translations_from_offset_factors(const float3 &offset,
const Span<float> factors,
const MutableSpan<float3> r_translations)
{
for (const int i : factors.index_range()) {
r_translations[i] = offset * factors[i];
}
}
static void calc_faces(const Sculpt &sd,
const Brush &brush,
const float3 &offset,
@ -84,7 +75,7 @@ static void calc_faces(const Sculpt &sd,
tls.translations.reinitialize(verts.size());
const MutableSpan<float3> translations = tls.translations;
translations_from_offset_factors(offset, factors, translations);
translations_from_offset_and_factors(offset, factors, translations);
write_translations(sd, object, positions_eval, verts, translations, positions_orig);
}
@ -131,7 +122,7 @@ static void calc_grids(const Sculpt &sd,
tls.translations.reinitialize(grid_verts_num);
const MutableSpan<float3> translations = tls.translations;
translations_from_offset_factors(offset, factors, translations);
translations_from_offset_and_factors(offset, factors, translations);
clip_and_lock_translations(sd, ss, positions, translations);
apply_translations(translations, grids, subdiv_ccg);
@ -176,7 +167,7 @@ static void calc_bmesh(const Sculpt &sd,
tls.translations.reinitialize(verts.size());
const MutableSpan<float3> translations = tls.translations;
translations_from_offset_factors(offset, factors, translations);
translations_from_offset_and_factors(offset, factors, translations);
clip_and_lock_translations(sd, ss, positions, translations);
apply_translations(translations, verts);

@ -0,0 +1,252 @@
/* 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_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_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 grab_cc {
struct LocalData {
Vector<float> factors;
Vector<float> distances;
Vector<float3> translations;
};
BLI_NOINLINE static void calc_silhouette_factors(const StrokeCache &cache,
const float3 &offset,
const Span<float3> normals,
const MutableSpan<float> factors)
{
BLI_assert(normals.size() == factors.size());
const float sign = math::sign(math::dot(cache.initial_normal, cache.grab_delta_symmetry));
const float3 test_dir = math::normalize(offset) * sign;
for (const int i : factors.index_range()) {
factors[i] *= std::max(math::dot(test_dir, normals[i]), 0.0f);
}
}
static void calc_faces(const Sculpt &sd,
const Brush &brush,
const float3 &offset,
const Span<float3> positions_eval,
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 OrigPositionData orig_data = orig_position_data_get_mesh(object, node);
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);
filter_region_clip_factors(ss, orig_data.positions, factors);
if (brush.flag & BRUSH_FRONTFACE) {
calc_front_face(cache.view_normal, orig_data.normals, factors);
}
tls.distances.reinitialize(verts.size());
const MutableSpan<float> distances = tls.distances;
calc_distance_falloff(
ss, orig_data.positions, eBrushFalloffShape(brush.falloff_shape), distances, factors);
apply_hardness_to_distances(cache, distances);
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, orig_data.positions, factors);
if (brush.flag2 & BRUSH_GRAB_SILHOUETTE) {
calc_silhouette_factors(cache, offset, orig_data.normals, factors);
}
tls.translations.reinitialize(verts.size());
const MutableSpan<float3> translations = tls.translations;
translations_from_offset_and_factors(offset, factors, translations);
write_translations(sd, object, positions_eval, verts, translations, positions_orig);
}
static void calc_grids(const Sculpt &sd,
Object &object,
const Brush &brush,
const float3 &offset,
PBVHNode &node,
LocalData &tls)
{
SculptSession &ss = *object.sculpt;
const StrokeCache &cache = *ss.cache;
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
const OrigPositionData orig_data = orig_position_data_get_grids(object, node);
const Span<int> grids = bke::pbvh::node_grid_indices(node);
const int grid_verts_num = grids.size() * key.grid_area;
tls.factors.reinitialize(grid_verts_num);
const MutableSpan<float> factors = tls.factors;
fill_factor_from_hide_and_mask(subdiv_ccg, grids, factors);
filter_region_clip_factors(ss, orig_data.positions, factors);
if (brush.flag & BRUSH_FRONTFACE) {
calc_front_face(cache.view_normal, orig_data.normals, factors);
}
tls.distances.reinitialize(grid_verts_num);
const MutableSpan<float> distances = tls.distances;
calc_distance_falloff(
ss, orig_data.positions, eBrushFalloffShape(brush.falloff_shape), distances, factors);
apply_hardness_to_distances(cache, distances);
calc_brush_strength_factors(cache, brush, distances, factors);
if (cache.automasking) {
auto_mask::calc_grids_factors(object, *cache.automasking, node, grids, factors);
}
calc_brush_texture_factors(ss, brush, orig_data.positions, factors);
if (brush.flag2 & BRUSH_GRAB_SILHOUETTE) {
calc_silhouette_factors(cache, offset, orig_data.normals, factors);
}
tls.translations.reinitialize(grid_verts_num);
const MutableSpan<float3> translations = tls.translations;
translations_from_offset_and_factors(offset, factors, translations);
clip_and_lock_translations(sd, ss, orig_data.positions, translations);
apply_translations(translations, grids, subdiv_ccg);
}
static void calc_bmesh(const Sculpt &sd,
Object &object,
const Brush &brush,
const float3 &offset,
PBVHNode &node,
LocalData &tls)
{
SculptSession &ss = *object.sculpt;
const StrokeCache &cache = *ss.cache;
const Set<BMVert *, 0> &verts = BKE_pbvh_bmesh_node_unique_verts(&node);
Array<float3> orig_positions(verts.size());
Array<float3> orig_normals(verts.size());
orig_position_data_gather_bmesh(*ss.bm_log, verts, orig_positions, orig_normals);
tls.factors.reinitialize(verts.size());
const MutableSpan<float> factors = tls.factors;
fill_factor_from_hide_and_mask(*ss.bm, verts, factors);
filter_region_clip_factors(ss, orig_positions, factors);
if (brush.flag & BRUSH_FRONTFACE) {
calc_front_face(cache.view_normal, orig_normals, factors);
}
tls.distances.reinitialize(verts.size());
const MutableSpan<float> distances = tls.distances;
calc_distance_falloff(
ss, orig_positions, eBrushFalloffShape(brush.falloff_shape), distances, factors);
apply_hardness_to_distances(cache, distances);
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, orig_positions, factors);
if (brush.flag2 & BRUSH_GRAB_SILHOUETTE) {
calc_silhouette_factors(cache, offset, orig_normals, factors);
}
tls.translations.reinitialize(verts.size());
const MutableSpan<float3> translations = tls.translations;
translations_from_offset_and_factors(offset, factors, translations);
clip_and_lock_translations(sd, ss, orig_positions, translations);
apply_translations(translations, verts);
}
} // namespace grab_cc
void do_grab_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes)
{
const SculptSession &ss = *object.sculpt;
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
float3 grab_delta = ss.cache->grab_delta_symmetry;
if (ss.cache->normal_weight > 0.0f) {
sculpt_project_v3_normal_align(ss, ss.cache->normal_weight, grab_delta);
}
grab_delta *= ss.cache->bstrength;
threading::EnumerableThreadSpecific<LocalData> all_tls;
switch (BKE_pbvh_type(*object.sculpt->pbvh)) {
case PBVH_FACES: {
Mesh &mesh = *static_cast<Mesh *>(object.data);
const PBVH &pbvh = *ss.pbvh;
const Span<float3> positions_eval = BKE_pbvh_get_vert_positions(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, grab_delta, positions_eval, *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) {
LocalData &tls = all_tls.local();
for (const int i : range) {
calc_grids(sd, object, brush, grab_delta, *nodes[i], tls);
}
});
break;
case PBVH_BMESH:
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
LocalData &tls = all_tls.local();
for (const int i : range) {
calc_bmesh(sd, object, brush, grab_delta, *nodes[i], tls);
}
});
break;
}
}
} // namespace blender::ed::sculpt_paint

@ -24,6 +24,7 @@ void do_draw_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
void do_draw_vector_displacement_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
void do_fill_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
void do_flatten_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
void do_grab_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
void do_inflate_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
/** A brush that modifies mask values instead of position. */
void do_mask_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);

@ -49,6 +49,9 @@ 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);
void translations_from_offset_and_factors(const float3 &offset,
Span<float> factors,
MutableSpan<float3> r_translations);
/**
* Note on the various positions arrays:
@ -90,6 +93,7 @@ void fill_factor_from_hide_and_mask(const BMesh &bm,
/**
* Disable brush influence when vertex normals point away from the view.
*/
void calc_front_face(const float3 &view_normal, Span<float3> normals, MutableSpan<float> factors);
void calc_front_face(const float3 &view_normal,
Span<float3> vert_normals,
Span<int> vert_indices,

@ -3816,7 +3816,7 @@ static void do_brush_action(const Scene &scene,
do_inflate_brush(sd, ob, nodes);
break;
case SCULPT_TOOL_GRAB:
SCULPT_do_grab_brush(sd, ob, nodes);
do_grab_brush(sd, ob, nodes);
break;
case SCULPT_TOOL_ROTATE:
SCULPT_do_rotate_brush(sd, ob, nodes);
@ -5597,6 +5597,18 @@ static void sculpt_restore_mesh(const Sculpt &sd, Object &ob)
SculptSession &ss = *ob.sculpt;
const Brush *brush = BKE_paint_brush_for_read(&sd.paint);
/* Brushes that also use original coordinates and will need a "restore" step.
* - SCULPT_TOOL_ROTATE
* - SCULPT_TOOL_THUMB
* - SCULPT_TOOL_ELASTIC_DEFORM
* - SCULPT_TOOL_BOUNDARY
* - SCULPT_TOOL_POSE
*/
if (ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB)) {
restore_from_undo_step(sd, ob);
return;
}
/* For the cloth brush it makes more sense to not restore the mesh state to keep running the
* simulation from the previous state. */
if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
@ -5969,6 +5981,7 @@ static void sculpt_stroke_update_step(bContext *C,
SCULPT_TOOL_CLAY,
SCULPT_TOOL_CLAY_STRIPS,
SCULPT_TOOL_CREASE,
SCULPT_TOOL_GRAB,
SCULPT_TOOL_DRAW,
SCULPT_TOOL_FILL,
SCULPT_TOOL_SCRAPE) &&
@ -6777,6 +6790,17 @@ void calc_front_face(const float3 &view_normal,
}
}
void calc_front_face(const float3 &view_normal,
const Span<float3> normals,
const MutableSpan<float> factors)
{
BLI_assert(normals.size() == factors.size());
for (const int i : normals.index_range()) {
const float dot = math::dot(view_normal, normals[i]);
factors[i] *= std::max(dot, 0.0f);
}
}
void calc_front_face(const float3 &view_normal,
const SubdivCCG &subdiv_ccg,
const Span<int> grids,
@ -7306,6 +7330,16 @@ void scale_factors(const MutableSpan<float> factors, const float strength)
factor *= strength;
}
}
void translations_from_offset_and_factors(const float3 &offset,
const Span<float> factors,
const MutableSpan<float3> r_translations)
{
BLI_assert(r_translations.size() == factors.size());
for (const int i : factors.index_range()) {
r_translations[i] = offset * factors[i];
}
}
OffsetIndices<int> create_node_vert_offsets(Span<PBVHNode *> nodes, Array<int> &node_data)
{

@ -112,14 +112,9 @@ static void sculpt_rake_rotate(const SculptSession &ss,
#endif
}
/**
* Align the grab delta to the brush normal.
*
* \param grab_delta: Typically from `ss.cache->grab_delta_symmetry`.
*/
static void sculpt_project_v3_normal_align(const SculptSession &ss,
const float normal_weight,
float grab_delta[3])
void sculpt_project_v3_normal_align(const SculptSession &ss,
const float normal_weight,
float grab_delta[3])
{
/* Signed to support grabbing in (to make a hole) as well as out. */
const float len_signed = dot_v3v3(ss.cache->sculpt_normal_symm, grab_delta);
@ -776,84 +771,6 @@ void SCULPT_do_pinch_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
});
}
static void do_grab_brush_task(Object &ob,
const Brush &brush,
const float *grab_delta,
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;
const float bstrength = ss.cache->bstrength;
SculptOrigVertData orig_data = SCULPT_orig_vert_data_init(ob, *node, undo::Type::Position);
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);
const bool grab_silhouette = brush.flag2 & BRUSH_GRAB_SILHOUETTE;
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, orig_data.co)) {
continue;
}
auto_mask::node_update(automask_data, vd);
float fade = bstrength * SCULPT_brush_strength_factor(ss,
brush,
orig_data.co,
sqrtf(test.dist),
orig_data.no,
nullptr,
vd.mask,
vd.vertex,
thread_id,
&automask_data);
if (grab_silhouette) {
float silhouette_test_dir[3];
normalize_v3_v3(silhouette_test_dir, grab_delta);
if (dot_v3v3(ss.cache->initial_normal, ss.cache->grab_delta_symmetry) < 0.0f) {
mul_v3_fl(silhouette_test_dir, -1.0f);
}
float vno[3];
copy_v3_v3(vno, orig_data.no);
fade *= max_ff(dot_v3v3(vno, silhouette_test_dir), 0.0f);
}
mul_v3_v3fl(proxy[vd.i], grab_delta, fade);
}
BKE_pbvh_vertex_iter_end;
}
void SCULPT_do_grab_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
{
using namespace blender;
SculptSession &ss = *ob.sculpt;
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
float grab_delta[3];
copy_v3_v3(grab_delta, ss.cache->grab_delta_symmetry);
if (ss.cache->normal_weight > 0.0f) {
sculpt_project_v3_normal_align(ss, ss.cache->normal_weight, grab_delta);
}
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
do_grab_brush_task(ob, brush, grab_delta, nodes[i]);
}
});
}
static void do_elastic_deform_brush_task(Object &ob,
const Brush &brush,
const float *grab_delta,

@ -786,6 +786,15 @@ bool SCULPT_stroke_is_first_brush_step(const blender::ed::sculpt_paint::StrokeCa
bool SCULPT_stroke_is_first_brush_step_of_symmetry_pass(
const blender::ed::sculpt_paint::StrokeCache &cache);
/**
* Align the grab delta to the brush normal.
*
* \param grab_delta: Typically from `ss.cache->grab_delta_symmetry`.
*/
void sculpt_project_v3_normal_align(const SculptSession &ss,
float normal_weight,
float grab_delta[3]);
/** \} */
/* -------------------------------------------------------------------- */
@ -1625,6 +1634,24 @@ BMLogEntry *get_bmesh_log_entry();
}
namespace blender::ed::sculpt_paint {
struct OrigPositionData {
Span<float3> positions;
Span<float3> normals;
};
/**
* Retrieve positions from the latest undo state. This is often used for modal actions that depend
* on the initial state of the geometry from before the start of the action.
*/
OrigPositionData orig_position_data_get_mesh(const Object &object, const PBVHNode &node);
OrigPositionData orig_position_data_get_grids(const Object &object, const PBVHNode &node);
void orig_position_data_gather_bmesh(const BMLog &bm_log,
const Set<BMVert *, 0> &verts,
MutableSpan<float3> positions,
MutableSpan<float3> normals);
}
/** \} */
void SCULPT_vertcos_to_key(Object &ob, KeyBlock *kb, blender::Span<blender::float3> vertCos);
@ -2023,7 +2050,6 @@ void SCULPT_do_thumb_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode
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_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);
void SCULPT_do_draw_sharp_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_slide_relax_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);

@ -2114,3 +2114,36 @@ void push_multires_mesh_end(bContext *C, const char *str)
/** \} */
} // namespace blender::ed::sculpt_paint::undo
namespace blender::ed::sculpt_paint {
OrigPositionData orig_position_data_get_mesh(const Object & /*object*/, const PBVHNode &node)
{
const undo::Node *unode = undo::get_node(&node, undo::Type::Position);
return {unode->position.as_span().take_front(unode->unique_verts_num),
unode->normal.as_span().take_front(unode->unique_verts_num)};
}
OrigPositionData orig_position_data_get_grids(const Object & /*object*/, const PBVHNode &node)
{
const undo::Node *unode = undo::get_node(&node, undo::Type::Position);
return {unode->position.as_span(), unode->normal.as_span()};
}
void orig_position_data_gather_bmesh(const BMLog &bm_log,
const Set<BMVert *, 0> &verts,
const MutableSpan<float3> positions,
const MutableSpan<float3> normals)
{
int i = 0;
for (const BMVert *vert : verts) {
const float *co;
const float *no;
BM_log_original_vert_data(&const_cast<BMLog &>(bm_log), const_cast<BMVert *>(vert), &co, &no);
positions[i] = co;
normals[i] = no;
i++;
}
}
} // namespace blender::ed::sculpt_paint