From 993361c2f01b250ba3f41ece372ee18eb91fc735 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 5 Jul 2024 10:20:11 -0400 Subject: [PATCH] Sculpt: Data oriented refactor for topology slide brush Part of #118145. Note that topology relax isn't affected here, only the slide part of the brush. I did make one small logic change-- the brush strength is now applied to the final translations rather than taken into account when adding the influence from every vertex neighbor. The behavior doesn't seem meaningfully different that way and it's easier to use common utilities. --- .../editors/sculpt_paint/CMakeLists.txt | 1 + .../sculpt_paint/brushes/topology_slide.cc | 377 ++++++++++++++++++ .../editors/sculpt_paint/brushes/types.hh | 1 + source/blender/editors/sculpt_paint/sculpt.cc | 7 +- .../sculpt_paint/sculpt_brush_types.cc | 89 +---- .../editors/sculpt_paint/sculpt_intern.hh | 2 +- 6 files changed, 390 insertions(+), 87 deletions(-) create mode 100644 source/blender/editors/sculpt_paint/brushes/topology_slide.cc diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 2d5c5576ca9..714ccd46dec 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -138,6 +138,7 @@ set(SRC brushes/smooth.cc brushes/smooth_mask.cc brushes/thumb.cc + brushes/topology_slide.cc brushes/types.hh ) diff --git a/source/blender/editors/sculpt_paint/brushes/topology_slide.cc b/source/blender/editors/sculpt_paint/brushes/topology_slide.cc new file mode 100644 index 00000000000..c3267a7a48d --- /dev/null +++ b/source/blender/editors/sculpt_paint/brushes/topology_slide.cc @@ -0,0 +1,377 @@ +/* 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_array_utils.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 topology_slide_cc { + +struct LocalData { + Vector positions; + Vector factors; + Vector distances; + Vector> vert_neighbors; + Vector translations; +}; + +BLI_NOINLINE static void calc_translation_directions(const Brush &brush, + const StrokeCache &cache, + const Span positions, + const MutableSpan r_translations) +{ + + switch (brush.slide_deform_type) { + case BRUSH_SLIDE_DEFORM_DRAG: + r_translations.fill(math::normalize(cache.location - cache.last_location)); + break; + case BRUSH_SLIDE_DEFORM_PINCH: + for (const int i : positions.index_range()) { + r_translations[i] = math::normalize(cache.location - positions[i]); + } + break; + case BRUSH_SLIDE_DEFORM_EXPAND: + for (const int i : positions.index_range()) { + r_translations[i] = math::normalize(positions[i] - cache.location); + } + break; + } +} + +static inline void add_neighbor_influence(const float3 &position, + const float3 &dir, + const float3 &neighbor_position, + float3 &translation) +{ + const float3 neighbor_disp = neighbor_position - position; + const float3 neighbor_dir = math::normalize(neighbor_disp); + if (math::dot(dir, neighbor_dir) > 0.0f) { + translation += neighbor_dir * math::dot(dir, neighbor_disp); + } +} + +BLI_NOINLINE static void calc_neighbor_influence(const Span vert_positions, + const Span positions, + const Span> vert_neighbors, + const MutableSpan translations) +{ + for (const int i : positions.index_range()) { + const float3 &position = positions[i]; + const float3 &dir = translations[i]; + + float3 final_translation(0); + for (const int neighbor : vert_neighbors[i]) { + add_neighbor_influence(position, dir, vert_positions[neighbor], final_translation); + } + + translations[i] = final_translation; + } +} + +BLI_NOINLINE static void calc_neighbor_influence(const SubdivCCG &subdiv_ccg, + const Span positions, + const Span grids, + const MutableSpan translations) +{ + const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg); + const Span elems = subdiv_ccg.grids; + for (const int i : grids.index_range()) { + const int node_start = i * key.grid_area; + const int grid = grids[i]; + for (const int y : IndexRange(key.grid_size)) { + for (const int x : IndexRange(key.grid_size)) { + const int offset = CCG_grid_xy_to_index(key.grid_size, x, y); + const int node_vert_index = node_start + offset; + + const float3 &position = positions[node_vert_index]; + const float3 &dir = translations[node_vert_index]; + + SubdivCCGCoord coord{}; + coord.grid_index = grid; + coord.x = x; + coord.y = y; + + SubdivCCGNeighbors neighbors; + BKE_subdiv_ccg_neighbor_coords_get(subdiv_ccg, coord, false, neighbors); + + float3 final_translation(0); + for (const SubdivCCGCoord neighbor : neighbors.coords) { + add_neighbor_influence( + position, + dir, + CCG_grid_elem_co(key, elems[neighbor.grid_index], neighbor.x, neighbor.y), + final_translation); + } + + translations[node_vert_index] = final_translation; + } + } + } +} + +BLI_NOINLINE static void calc_neighbor_influence(const Span positions, + const Set &verts, + const MutableSpan translations) +{ + Vector neighbors; + int i = 0; + for (BMVert *vert : verts) { + const float3 &position = positions[i]; + const float3 &dir = translations[i]; + + float3 final_translation(0); + neighbors.clear(); + for (BMVert *neighbor : vert_neighbors_get_bmesh(*vert, neighbors)) { + add_neighbor_influence(position, dir, neighbor->co, final_translation); + } + + translations[i] = final_translation; + i++; + } +} + +static void calc_faces(const Sculpt &sd, + const Brush &brush, + const Span positions_eval, + const OffsetIndices faces, + const Span corner_verts, + const GroupedSpan vert_to_face_map, + const Span hide_poly, + const PBVHNode &node, + Object &object, + LocalData &tls, + const MutableSpan positions_orig) +{ + SculptSession &ss = *object.sculpt; + const StrokeCache &cache = *ss.cache; + Mesh &mesh = *static_cast(object.data); + + const OrigPositionData orig_data = orig_position_data_get_mesh(object, node); + const Span verts = bke::pbvh::node_unique_verts(node); + + tls.positions.reinitialize(verts.size()); + const MutableSpan positions = tls.positions; + array_utils::gather(positions_eval, verts, positions); + + tls.factors.reinitialize(verts.size()); + const MutableSpan factors = tls.factors; + fill_factor_from_hide_and_mask(mesh, verts, factors); + filter_region_clip_factors(ss, positions_eval, verts, factors); + if (brush.flag & BRUSH_FRONTFACE) { + calc_front_face(cache.view_normal, orig_data.normals, factors); + } + + tls.distances.reinitialize(verts.size()); + const MutableSpan distances = tls.distances; + calc_brush_distances( + ss, orig_data.positions, eBrushFalloffShape(brush.falloff_shape), distances); + filter_distances_with_radius(cache.radius, 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); + + scale_factors(factors, cache.bstrength); + + tls.vert_neighbors.reinitialize(verts.size()); + calc_vert_neighbors(faces, corner_verts, vert_to_face_map, hide_poly, verts, tls.vert_neighbors); + const Span> vert_neighbors = tls.vert_neighbors; + + tls.translations.reinitialize(verts.size()); + const MutableSpan translations = tls.translations; + calc_translation_directions(brush, cache, positions, translations); + calc_neighbor_influence(positions_eval, positions, vert_neighbors, translations); + scale_translations(translations, factors); + + write_translations(sd, object, orig_data.positions, verts, translations, positions_orig); +} + +static void calc_grids( + const Sculpt &sd, Object &object, const Brush &brush, const 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 grids = bke::pbvh::node_grid_indices(node); + const int grid_verts_num = grids.size() * key.grid_area; + + tls.positions.reinitialize(grid_verts_num); + MutableSpan positions = tls.positions; + gather_grids_positions(subdiv_ccg, grids, positions); + + tls.factors.reinitialize(grid_verts_num); + const MutableSpan 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, grids, factors); + } + + tls.distances.reinitialize(grid_verts_num); + const MutableSpan distances = tls.distances; + calc_brush_distances( + ss, orig_data.positions, eBrushFalloffShape(brush.falloff_shape), distances); + filter_distances_with_radius(cache.radius, 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); + + scale_factors(factors, cache.bstrength); + + tls.translations.reinitialize(grid_verts_num); + const MutableSpan translations = tls.translations; + calc_translation_directions(brush, cache, positions, translations); + calc_neighbor_influence(subdiv_ccg, positions, grids, translations); + scale_translations(translations, factors); + + 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, PBVHNode &node, LocalData &tls) +{ + SculptSession &ss = *object.sculpt; + const StrokeCache &cache = *ss.cache; + + const Set &verts = BKE_pbvh_bmesh_node_unique_verts(&node); + + tls.positions.reinitialize(verts.size()); + const MutableSpan positions = tls.positions; + gather_bmesh_positions(verts, positions); + + Array orig_positions(verts.size()); + Array orig_normals(verts.size()); + orig_position_data_gather_bmesh(*ss.bm_log, verts, orig_positions, orig_normals); + + tls.factors.reinitialize(verts.size()); + const MutableSpan 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 distances = tls.distances; + calc_brush_distances(ss, orig_positions, eBrushFalloffShape(brush.falloff_shape), distances); + filter_distances_with_radius(cache.radius, 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); + + scale_factors(factors, cache.bstrength); + + tls.translations.reinitialize(verts.size()); + const MutableSpan translations = tls.translations; + calc_translation_directions(brush, cache, positions, translations); + calc_neighbor_influence(positions, verts, translations); + scale_translations(translations, factors); + + clip_and_lock_translations(sd, ss, orig_positions, translations); + apply_translations(translations, verts); +} + +} // namespace topology_slide_cc + +void do_topology_slide_brush(const Sculpt &sd, Object &object, Span nodes) +{ + const SculptSession &ss = *object.sculpt; + const Brush &brush = *BKE_paint_brush_for_read(&sd.paint); + + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache)) { + return; + } + + threading::EnumerableThreadSpecific all_tls; + switch (BKE_pbvh_type(*object.sculpt->pbvh)) { + case PBVH_FACES: { + Mesh &mesh = *static_cast(object.data); + const PBVH &pbvh = *ss.pbvh; + const Span positions_eval = BKE_pbvh_get_vert_positions(pbvh); + MutableSpan positions_orig = mesh.vert_positions_for_write(); + const OffsetIndices faces = mesh.faces(); + const Span corner_verts = mesh.corner_verts(); + const bke::AttributeAccessor attributes = mesh.attributes(); + const VArraySpan hide_poly = *attributes.lookup(".hide_poly", bke::AttrDomain::Face); + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + LocalData &tls = all_tls.local(); + threading::isolate_task([&]() { + for (const int i : range) { + calc_faces(sd, + brush, + positions_eval, + faces, + corner_verts, + ss.vert_to_face_map, + hide_poly, + *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, *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, *nodes[i], tls); + } + }); + break; + } +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/brushes/types.hh b/source/blender/editors/sculpt_paint/brushes/types.hh index 00221e1dd9c..990c3a03f74 100644 --- a/source/blender/editors/sculpt_paint/brushes/types.hh +++ b/source/blender/editors/sculpt_paint/brushes/types.hh @@ -53,5 +53,6 @@ void do_smooth_mask_brush(const Sculpt &sd, Span nodes, float brush_strength); void do_thumb_brush(const Sculpt &sd, Object &object, Span nodes); +void do_topology_slide_brush(const Sculpt &sd, Object &object, Span nodes); } // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index 551e7d54123..a5f95153104 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -3884,7 +3884,12 @@ static void do_brush_action(const Scene &scene, do_elastic_deform_brush(sd, ob, nodes); break; case SCULPT_TOOL_SLIDE_RELAX: - SCULPT_do_slide_relax_brush(sd, ob, nodes); + if (ss.cache->alt_smooth) { + SCULPT_do_topology_relax_brush(sd, ob, nodes); + } + else { + do_topology_slide_brush(sd, ob, nodes); + } break; case SCULPT_TOOL_BOUNDARY: boundary::do_boundary_brush(sd, ob, nodes); diff --git a/source/blender/editors/sculpt_paint/sculpt_brush_types.cc b/source/blender/editors/sculpt_paint/sculpt_brush_types.cc index b15fa2f71a3..2e4c2df0ce9 100644 --- a/source/blender/editors/sculpt_paint/sculpt_brush_types.cc +++ b/source/blender/editors/sculpt_paint/sculpt_brush_types.cc @@ -386,77 +386,6 @@ void SCULPT_do_layer_brush(const Sculpt &sd, Object &ob, Span nodes) /** \name Sculpt Topology Brush * \{ */ -static void do_topology_slide_task(Object &ob, const Brush &brush, PBVHNode *node) -{ - using namespace blender::ed::sculpt_paint; - SculptSession &ss = *ob.sculpt; - - PBVHVertexIter vd; - const MutableSpan proxy = BKE_pbvh_node_add_proxy(*ss.pbvh, *node).co; - - 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); - - 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); - - const float fade = SCULPT_brush_strength_factor(ss, - brush, - orig_data.co, - sqrtf(test.dist), - orig_data.no, - nullptr, - vd.mask, - vd.vertex, - thread_id, - &automask_data); - float current_disp[3]; - float current_disp_norm[3]; - float final_disp[3] = {0.0f, 0.0f, 0.0f}; - - switch (brush.slide_deform_type) { - case BRUSH_SLIDE_DEFORM_DRAG: - sub_v3_v3v3(current_disp, ss.cache->location, ss.cache->last_location); - break; - case BRUSH_SLIDE_DEFORM_PINCH: - sub_v3_v3v3(current_disp, ss.cache->location, vd.co); - break; - case BRUSH_SLIDE_DEFORM_EXPAND: - sub_v3_v3v3(current_disp, vd.co, ss.cache->location); - break; - } - - normalize_v3_v3(current_disp_norm, current_disp); - mul_v3_v3fl(current_disp, current_disp_norm, ss.cache->bstrength); - - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.vertex, ni) { - float vertex_disp[3]; - float vertex_disp_norm[3]; - sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.vertex), vd.co); - normalize_v3_v3(vertex_disp_norm, vertex_disp); - if (dot_v3v3(current_disp_norm, vertex_disp_norm) > 0.0f) { - madd_v3_v3fl(final_disp, vertex_disp_norm, dot_v3v3(current_disp, vertex_disp)); - } - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - - mul_v3_v3fl(proxy[vd.i], final_disp, fade); - } - BKE_pbvh_vertex_iter_end; -} - namespace blender::ed::sculpt_paint::smooth { static void relax_vertex_interior(SculptSession &ss, @@ -654,7 +583,7 @@ static void do_topology_relax_task(Object &ob, const Brush &brush, PBVHNode *nod BKE_pbvh_vertex_iter_end; } -void SCULPT_do_slide_relax_brush(const Sculpt &sd, Object &ob, Span nodes) +void SCULPT_do_topology_relax_brush(const Sculpt &sd, Object &ob, Span nodes) { using namespace blender; SculptSession &ss = *ob.sculpt; @@ -665,21 +594,11 @@ void SCULPT_do_slide_relax_brush(const Sculpt &sd, Object &ob, Span } BKE_curvemapping_init(brush.curve); - - if (ss.cache->alt_smooth) { - SCULPT_boundary_info_ensure(ob); - for (int i = 0; i < 4; i++) { - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - for (const int i : range) { - do_topology_relax_task(ob, brush, nodes[i]); - } - }); - } - } - else { + SCULPT_boundary_info_ensure(ob); + for (int i = 0; i < 4; i++) { threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (const int i : range) { - do_topology_slide_task(ob, brush, nodes[i]); + do_topology_relax_task(ob, brush, nodes[i]); } }); } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.hh b/source/blender/editors/sculpt_paint/sculpt_intern.hh index e89ff8ff4f4..8e9fa04f65a 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/sculpt_intern.hh @@ -2056,7 +2056,7 @@ float clay_thumb_get_stabilized_pressure(const blender::ed::sculpt_paint::Stroke void SCULPT_do_snake_hook_brush(const Sculpt &sd, Object &ob, blender::Span nodes); void SCULPT_do_layer_brush(const Sculpt &sd, Object &ob, blender::Span nodes); -void SCULPT_do_slide_relax_brush(const Sculpt &sd, Object &ob, blender::Span nodes); +void SCULPT_do_topology_relax_brush(const Sculpt &sd, Object &ob, blender::Span nodes); /** \} */