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); /** \} */