diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index cca98e8a326..d67c5e2fb72 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -138,6 +138,7 @@ set(SRC brushes/multires_displacement_eraser.cc brushes/pinch.cc brushes/multires_displacement_smear.cc + brushes/relax_face_sets.cc brushes/rotate.cc brushes/scrape.cc brushes/smooth.cc diff --git a/source/blender/editors/sculpt_paint/brushes/relax_face_sets.cc b/source/blender/editors/sculpt_paint/brushes/relax_face_sets.cc new file mode 100644 index 00000000000..d09c237eef9 --- /dev/null +++ b/source/blender/editors/sculpt_paint/brushes/relax_face_sets.cc @@ -0,0 +1,1033 @@ +/* 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 "BKE_subdiv_ccg.hh" + +#include "BLI_enumerable_thread_specific.hh" +#include "BLI_math_base.hh" +#include "BLI_math_geom.h" +#include "BLI_math_vector.hh" +#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 relax_face_sets_cc { + +struct MeshLocalData { + Vector factors; + Vector distances; + Vector> vert_neighbors; +}; + +struct GridLocalData { + Vector factors; + Vector distances; + Vector> vert_neighbors; +}; + +struct BMeshLocalData { + Vector factors; + Vector distances; + Vector> vert_neighbors; +}; + +static std::array iteration_strengths(const float strength, const int stroke_iteration) +{ + if (stroke_iteration % 3 == 0) { + return {strength, strength, strength, strength}; + } + + /* This operations needs a strength tweak as the relax deformation is too weak by default. */ + const float modified_strength = strength * 1.5f; + return {modified_strength, modified_strength, strength, strength}; +} + +BLI_NOINLINE static void filter_factors_on_face_sets_mesh(const GroupedSpan vert_to_face_map, + const int *face_sets, + const bool relax_face_sets, + const Span verts, + const MutableSpan factors) +{ + BLI_assert(verts.size() == factors.size()); + + for (const int i : verts.index_range()) { + if (relax_face_sets == + face_set::vert_has_unique_face_set(vert_to_face_map, face_sets, verts[i])) + { + factors[i] = 0.0f; + } + } +} +BLI_NOINLINE static void filter_factors_on_face_sets_grids(const GroupedSpan vert_to_face_map, + const Span corner_verts, + const OffsetIndices faces, + const SubdivCCG &subdiv_ccg, + const int *face_sets, + const bool relax_face_sets, + const Span grids, + const MutableSpan factors) +{ + const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg); + BLI_assert(grids.size() * key.grid_area == factors.size()); + + for (const int i : grids.index_range()) { + const int start = i * key.grid_area; + 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); + if (factors[start + offset] == 0.0f) { + continue; + } + + SubdivCCGCoord coord{}; + coord.grid_index = grids[i]; + coord.x = x; + coord.y = y; + if (relax_face_sets == + face_set::vert_has_unique_face_set( + vert_to_face_map, corner_verts, faces, face_sets, subdiv_ccg, coord)) + { + factors[start + offset] = 0.0f; + } + } + } + } +} +static void filter_factors_on_face_sets_bmesh(const bool relax_face_sets, + const Set verts, + const MutableSpan factors) +{ + BLI_assert(verts.size() == factors.size()); + + int i = 0; + for (const BMVert *vert : verts) { + if (relax_face_sets == face_set::vert_has_unique_face_set(vert)) { + factors[i] = 0.0f; + } + i++; + } +} + +static float3 translation_to_plane(const float3 current_position, + const float3 normal, + const float3 smoothed_position) +{ + float4 plane; + plane_from_point_normal_v3(plane, current_position, normal); + + float3 smooth_closest_plane; + closest_to_plane_v3(smooth_closest_plane, plane, smoothed_position); + + return smooth_closest_plane - current_position; +} + +/* -------------------------------------------------------------------- */ +/** \name Relax Vertex + * \{ */ + +static Vector filtered_neighbors(const Span neighbors, + const bool filter_boundary_face_sets, + FunctionRef is_unique_element_fn, + FunctionRef is_boundary_element_fn) +{ + Vector result; + for (const int vert : neighbors) { + /* If we are filtering face sets, then we only want to affect vertices that have more than one + * face set, i.e. are on the boundary of a face set and another face set. */ + if (filter_boundary_face_sets && is_unique_element_fn(vert)) { + continue; + } + + /* When the vertex to relax is boundary, use only connected boundary vertices for the average + * position. */ + if (is_boundary_element_fn && is_boundary_element_fn(vert)) { + continue; + } + + result.append(vert); + } + return result; +} + +static bool get_normal_boundary(const float3 ¤t_position, + const Span vert_positions, + const Span neighbors, + float3 &r_new_normal) +{ + /* If we are not dealing with a corner vertex, skip this step.*/ + if (neighbors.size() != 2) { + return false; + } + + float3 normal(0.0f, 0.0f, 0.0f); + for (const int vert : neighbors) { + const float3 to_neighbor = vert_positions[vert] - current_position; + normal += math::normalize(to_neighbor); + } + + r_new_normal = math::normalize(normal); + + return true; +} + +static bool get_average_position(const Span vert_positions, + const Span neighbors, + float3 &r_new_position) +{ + if (neighbors.size() == 0) { + return false; + } + + float3 average_position(0.0f, 0.0f, 0.0f); + for (const int vert : neighbors) { + average_position += vert_positions[vert]; + } + + average_position *= math::rcp(float(neighbors.size())); + r_new_position = average_position; + + return true; +} + +BLI_NOINLINE static Vector filtered_neighbors( + const Span neighbors, + const bool filter_boundary_face_sets, + FunctionRef is_unique_element_fn, + FunctionRef is_boundary_element_fn) +{ + Vector result; + for (const SubdivCCGCoord coord : neighbors) { + /* If we are filtering face sets, then we only want to affect vertices that have more than one + * face set, i.e. are on the boundary of a face set and another face set. */ + if (filter_boundary_face_sets && is_unique_element_fn(coord)) { + continue; + } + + /* When the vertex to relax is boundary, use only connected boundary vertices for the average + * position. */ + if (is_boundary_element_fn && is_boundary_element_fn(coord)) { + continue; + } + + result.append(coord); + } + return result; +} + +BLI_NOINLINE static bool get_normal_boundary(const CCGKey &key, + const Span elems, + const float3 ¤t_position, + const Span neighbors, + float3 &r_new_normal) +{ + /* If we are not dealing with a corner vertex, skip this step.*/ + if (neighbors.size() != 2) { + return false; + } + + float3 normal(0.0f, 0.0f, 0.0f); + for (const SubdivCCGCoord &coord : neighbors) { + const float3 to_neighbor = CCG_grid_elem_co(key, elems[coord.grid_index], coord.x, coord.y) - + current_position; + normal += math::normalize(to_neighbor); + } + + r_new_normal = math::normalize(normal); + + return true; +} + +BLI_NOINLINE static bool get_average_position(const CCGKey &key, + const Span elems, + const Span positions, + const Span neighbors, + const int current_grid, + const int current_grid_start, + float3 &r_new_position) +{ + if (neighbors.size() == 0) { + return false; + } + + float3 average_position(0.0f, 0.0f, 0.0f); + for (const SubdivCCGCoord &coord : neighbors) { + if (current_grid == coord.grid_index) { + const int offset = CCG_grid_xy_to_index(key.grid_size, coord.x, coord.y); + average_position += positions[current_grid_start + offset]; + } + else { + average_position += CCG_grid_elem_co(key, elems[coord.grid_index], coord.x, coord.y); + } + } + + average_position *= math::rcp(float(neighbors.size())); + r_new_position = average_position; + + return true; +} + +static Vector filtered_neighbors( + const Span neighbors, + const bool filter_boundary_face_sets, + FunctionRef is_unique_element_fn, + FunctionRef is_boundary_element_fn) +{ + Vector result; + for (BMVert *vert : neighbors) { + /* If we are filtering face sets, then we only want to affect vertices that have more than one + * face set, i.e. are on the boundary of a face set and another face set. */ + if (filter_boundary_face_sets && is_unique_element_fn(vert)) { + continue; + } + + /* When the vertex to relax is boundary, use only connected boundary vertices for the average + * position. */ + if (is_boundary_element_fn && is_boundary_element_fn(vert)) { + continue; + } + + result.append(vert); + } + return result; +} + +static bool get_normal_boundary(const float3 ¤t_position, + const Span neighbors, + float3 &r_new_normal) +{ + /* If we are not dealing with a corner vertex, skip this step.*/ + if (neighbors.size() != 2) { + return false; + } + + float3 normal(0.0f, 0.0f, 0.0f); + int i = 0; + for (BMVert *vert : neighbors) { + const float3 neighbor_pos = vert->co; + const float3 to_neighbor = neighbor_pos - current_position; + normal += math::normalize(to_neighbor); + i++; + } + + r_new_normal = math::normalize(normal); + + return true; +} + +static bool get_average_position(const Span neighbors, float3 &r_new_position) +{ + if (neighbors.size() == 0) { + return false; + } + + float3 average_position(0.0f, 0.0f, 0.0f); + int i = 0; + for (BMVert *vert : neighbors) { + average_position += vert->co; + i++; + } + + average_position *= math::rcp(float(neighbors.size())); + r_new_position = average_position; + + return true; +} + +/** \} */ +BLI_NOINLINE static void calc_factors_faces(const Brush &brush, + const Span positions_eval, + const Span vert_normals, + const PBVHNode &node, + const float strength, + const bool relax_face_sets, + Object &object, + MeshLocalData &tls, + const MutableSpan factors) +{ + SculptSession &ss = *object.sculpt; + const StrokeCache &cache = *ss.cache; + const Mesh &mesh = *static_cast(object.data); + + const Span verts = bke::pbvh::node_unique_verts(node); + + 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, vert_normals, verts, factors); + } + + tls.distances.reinitialize(verts.size()); + const MutableSpan distances = tls.distances; + calc_brush_distances( + ss, positions_eval, verts, 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); + } + + scale_factors(factors, strength); + + calc_brush_texture_factors(ss, brush, positions_eval, verts, factors); + + filter_factors_on_face_sets_mesh( + ss.vert_to_face_map, ss.face_sets, relax_face_sets, verts, factors); +} + +BLI_NOINLINE static void calc_relaxed_translations_faces(const OffsetIndices faces, + const Span corner_verts, + const int *face_sets, + const GroupedSpan vert_to_face_map, + const BitSpan boundary_verts, + const Span hide_poly, + const Span verts, + const Span vert_positions, + const Span vert_normals, + const bool relax_face_sets, + MeshLocalData &tls, + const Span factors, + const MutableSpan translations) +{ + BLI_assert(verts.size() == factors.size()); + BLI_assert(verts.size() == translations.size()); + + tls.vert_neighbors.reinitialize(verts.size()); + calc_vert_neighbors_interior( + faces, corner_verts, vert_to_face_map, boundary_verts, hide_poly, verts, tls.vert_neighbors); + const Span> vert_neighbors = tls.vert_neighbors; + + for (const int i : verts.index_range()) { + if (factors[i] == 0.0f) { + translations[i] = float3(0.0f, 0.0f, 0.0f); + continue; + } + + /* Don't modify corner vertices */ + if (vert_neighbors[i].size() <= 2) { + translations[i] = float3(0.0f, 0.0f, 0.0f); + continue; + } + + Vector neighbors; + if (boundary_verts[verts[i]]) { + neighbors = filtered_neighbors( + vert_neighbors[i], + relax_face_sets, + [&](const int vert) { + return face_set::vert_has_unique_face_set(vert_to_face_map, face_sets, vert); + }, + [&](const int vert) { return !boundary_verts[vert]; }); + } + else { + neighbors = filtered_neighbors(vert_neighbors[i], + relax_face_sets, + [&](const int vert) { + return face_set::vert_has_unique_face_set( + vert_to_face_map, face_sets, vert); + }, + {}); + } + + /* Smoothed position calculation */ + float3 smoothed_position; + const bool has_new_position = get_average_position( + vert_positions, neighbors, smoothed_position); + + if (!has_new_position) { + translations[i] = float3(0.0f, 0.0f, 0.0f); + continue; + } + + /* Normal Calculation */ + float3 normal; + if (boundary_verts[verts[i]]) { + bool has_boundary_normal = get_normal_boundary( + vert_positions[verts[i]], vert_positions, neighbors, normal); + + if (!has_boundary_normal) { + normal = vert_normals[verts[i]]; + } + } + else { + normal = vert_normals[verts[i]]; + } + + if (math::is_zero(normal)) { + translations[i] = float3(0.0f, 0.0f, 0.0f); + continue; + } + + const float3 translation = translation_to_plane( + vert_positions[verts[i]], normal, smoothed_position); + + translations[i] = translation * factors[i]; + } +} + +BLI_NOINLINE static void apply_positions_faces(const Sculpt &sd, + const Span positions_eval, + const Span verts, + Object &object, + const MutableSpan translations, + const MutableSpan positions_orig) +{ + write_translations(sd, object, positions_eval, verts, translations, positions_orig); +} + +static void do_relax_face_sets_brush_mesh(const Sculpt &sd, + const Brush &brush, + Object &object, + const Span nodes, + const float strength, + const bool relax_face_sets) +{ + const SculptSession &ss = *object.sculpt; + Mesh &mesh = *static_cast(object.data); + 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); + + const PBVH &pbvh = *ss.pbvh; + + const Span positions_eval = BKE_pbvh_get_vert_positions(pbvh); + const Span vert_normals = BKE_pbvh_get_vert_normals(pbvh); + MutableSpan positions_orig = mesh.vert_positions_for_write(); + + Array node_offset_data; + const OffsetIndices node_vert_offsets = create_node_vert_offsets(nodes, node_offset_data); + + Array translations(node_vert_offsets.total_size()); + Array factors(node_vert_offsets.total_size()); + + threading::EnumerableThreadSpecific all_tls; + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + MeshLocalData &tls = all_tls.local(); + for (const int i : range) { + calc_factors_faces(brush, + positions_eval, + vert_normals, + *nodes[i], + strength, + relax_face_sets, + object, + tls, + factors.as_mutable_span().slice(node_vert_offsets[i])); + } + }); + + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + MeshLocalData &tls = all_tls.local(); + for (const int i : range) { + calc_relaxed_translations_faces(faces, + corner_verts, + ss.face_sets, + ss.vert_to_face_map, + ss.vertex_info.boundary, + hide_poly, + bke::pbvh::node_unique_verts(*nodes[i]), + positions_eval, + vert_normals, + relax_face_sets, + tls, + factors.as_span().slice(node_vert_offsets[i]), + translations.as_mutable_span().slice(node_vert_offsets[i])); + } + }); + + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + apply_positions_faces(sd, + positions_eval, + bke::pbvh::node_unique_verts(*nodes[i]), + object, + translations.as_mutable_span().slice(node_vert_offsets[i]), + positions_orig); + } + }); +} + +BLI_NOINLINE static void calc_factors_grids(const Brush &brush, + const Span corner_verts, + const OffsetIndices faces, + const PBVHNode &node, + const float strength, + const bool relax_face_sets, + Object &object, + GridLocalData &tls, + const MutableSpan positions, + const MutableSpan factors) +{ + 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 Span grids = bke::pbvh::node_grid_indices(node); + const int grid_verts_num = grids.size() * key.grid_area; + + gather_grids_positions(subdiv_ccg, grids, positions); + + fill_factor_from_hide_and_mask(subdiv_ccg, grids, factors); + filter_region_clip_factors(ss, positions, factors); + if (brush.flag & BRUSH_FRONTFACE) { + calc_front_face(cache.view_normal, subdiv_ccg, grids, factors); + } + + tls.distances.reinitialize(grid_verts_num); + const MutableSpan distances = tls.distances; + calc_brush_distances(ss, 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); + } + + scale_factors(factors, strength); + + calc_brush_texture_factors(ss, brush, positions, factors); + + filter_factors_on_face_sets_grids(ss.vert_to_face_map, + corner_verts, + faces, + subdiv_ccg, + ss.face_sets, + relax_face_sets, + grids, + factors); +} + +BLI_NOINLINE static void calc_relaxed_translations_grids(const OffsetIndices faces, + const Span corner_verts, + const int *face_sets, + const GroupedSpan vert_to_face_map, + const BitSpan boundary_verts, + const Span grids, + const bool relax_face_sets, + Object &object, + GridLocalData &tls, + const Span factors, + const Span positions, + const MutableSpan translations) +{ + SculptSession &ss = *object.sculpt; + SubdivCCG &subdiv_ccg = *ss.subdiv_ccg; + const Span elems = subdiv_ccg.grids; + const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg); + + const int grid_verts_num = grids.size() * key.grid_area; + BLI_assert(grid_verts_num == translations.size()); + BLI_assert(grid_verts_num == factors.size()); + + tls.vert_neighbors.reinitialize(grid_verts_num); + calc_vert_neighbors_interior( + faces, corner_verts, boundary_verts, subdiv_ccg, grids, tls.vert_neighbors); + const Span> vert_neighbors = tls.vert_neighbors; + + for (const int i : grids.index_range()) { + CCGElem *elem = elems[grids[i]]; + const int start = i * key.grid_area; + 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 grid_idx = start + offset; + if (factors[grid_idx] == 0.0f) { + translations[grid_idx] = float3(0.0f, 0.0f, 0.0f); + continue; + } + + SubdivCCGCoord coord{}; + coord.grid_index = grids[i]; + coord.x = x; + coord.y = y; + + const Span vert_neighbor = vert_neighbors[start + offset]; + /* Don't modify corner vertices */ + if (vert_neighbor.size() <= 2) { + translations[grid_idx] = float3(0.0f, 0.0f, 0.0f); + continue; + } + + bool is_boundary = BKE_subdiv_ccg_coord_is_mesh_boundary( + faces, corner_verts, boundary_verts, subdiv_ccg, coord); + + Vector neighbors; + if (is_boundary) { + neighbors = filtered_neighbors( + vert_neighbor, + relax_face_sets, + [&](const SubdivCCGCoord &neighbor) { + return face_set::vert_has_unique_face_set( + vert_to_face_map, corner_verts, faces, face_sets, subdiv_ccg, neighbor); + }, + [&](const SubdivCCGCoord &neighbor) { + return !BKE_subdiv_ccg_coord_is_mesh_boundary( + faces, corner_verts, boundary_verts, subdiv_ccg, neighbor); + }); + } + else { + neighbors = filtered_neighbors( + vert_neighbor, + relax_face_sets, + [&](const SubdivCCGCoord &neighbor) { + return face_set::vert_has_unique_face_set( + vert_to_face_map, corner_verts, faces, face_sets, subdiv_ccg, neighbor); + }, + {}); + } + + /* Smoothed position calculation */ + float3 smoothed_position; + const bool has_new_position = get_average_position( + key, elems, positions, neighbors, grids[i], start, smoothed_position); + + if (!has_new_position) { + translations[grid_idx] = float3(0.0f, 0.0f, 0.0f); + continue; + } + + /* Normal Calculation */ + float3 normal; + if (is_boundary) { + bool has_boundary_normal = get_normal_boundary( + key, elems, positions[grid_idx], neighbors, normal); + + if (!has_boundary_normal) { + normal = CCG_elem_offset_no(key, elem, offset); + } + } + else { + normal = CCG_elem_offset_no(key, elem, offset); + } + + if (math::is_zero(normal)) { + translations[grid_idx] = float3(0.0f, 0.0f, 0.0f); + continue; + } + + const float3 translation = translation_to_plane( + positions[grid_idx], normal, smoothed_position); + + translations[grid_idx] = translation * factors[grid_idx]; + } + } + } +} + +BLI_NOINLINE static void apply_positions_grids(const Sculpt &sd, + const Span grids, + Object &object, + const Span positions, + const MutableSpan translations) +{ + SculptSession &ss = *object.sculpt; + SubdivCCG &subdiv_ccg = *ss.subdiv_ccg; + + clip_and_lock_translations(sd, ss, positions, translations); + apply_translations(translations, grids, subdiv_ccg); +} + +static void do_relax_face_sets_brush_grids(const Sculpt &sd, + const Brush &brush, + Object &object, + const Span nodes, + const float strength, + const bool relax_face_sets) +{ + const SculptSession &ss = *object.sculpt; + SubdivCCG &subdiv_ccg = *ss.subdiv_ccg; + const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg); + + Mesh &mesh = *static_cast(object.data); + 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); + + Array node_offset_data; + const OffsetIndices node_vert_offsets = create_node_vert_offsets( + nodes, key, node_offset_data); + + Array current_positions(node_vert_offsets.total_size()); + Array translations(node_vert_offsets.total_size()); + Array factors(node_vert_offsets.total_size()); + + threading::EnumerableThreadSpecific all_tls; + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + GridLocalData &tls = all_tls.local(); + for (const int i : range) { + calc_factors_grids(brush, + corner_verts, + faces, + *nodes[i], + strength, + relax_face_sets, + object, + tls, + current_positions.as_mutable_span().slice(node_vert_offsets[i]), + factors.as_mutable_span().slice(node_vert_offsets[i])); + } + }); + + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + GridLocalData &tls = all_tls.local(); + for (const int i : range) { + calc_relaxed_translations_grids(faces, + corner_verts, + ss.face_sets, + ss.vert_to_face_map, + ss.vertex_info.boundary, + bke::pbvh::node_grid_indices(*nodes[i]), + relax_face_sets, + object, + tls, + factors.as_span().slice(node_vert_offsets[i]), + current_positions.as_span().slice(node_vert_offsets[i]), + translations.as_mutable_span().slice(node_vert_offsets[i])); + } + }); + + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + apply_positions_grids(sd, + bke::pbvh::node_grid_indices(*nodes[i]), + object, + current_positions.as_mutable_span().slice(node_vert_offsets[i]), + translations.as_mutable_span().slice(node_vert_offsets[i])); + } + }); +} + +static void calc_factors_bmesh(Object &object, + const Brush &brush, + PBVHNode &node, + const float strength, + const bool relax_face_sets, + BMeshLocalData &tls, + MutableSpan positions, + MutableSpan factors) +{ + SculptSession &ss = *object.sculpt; + const StrokeCache &cache = *ss.cache; + + const Set &verts = BKE_pbvh_bmesh_node_unique_verts(&node); + + gather_bmesh_positions(verts, positions); + + fill_factor_from_hide_and_mask(*ss.bm, verts, factors); + filter_region_clip_factors(ss, positions, factors); + if (brush.flag & BRUSH_FRONTFACE) { + calc_front_face(cache.view_normal, verts, factors); + } + + tls.distances.reinitialize(verts.size()); + const MutableSpan distances = tls.distances; + calc_brush_distances(ss, 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); + } + + scale_factors(factors, strength); + + calc_brush_texture_factors(ss, brush, positions, factors); + filter_factors_on_face_sets_bmesh(relax_face_sets, verts, factors); +} + +BLI_NOINLINE static void calc_relaxed_translations_bmesh(const Set &verts, + const Span positions, + const bool relax_face_sets, + BMeshLocalData &tls, + const Span factors, + const MutableSpan translations) +{ + BLI_assert(verts.size() == factors.size()); + BLI_assert(verts.size() == translations.size()); + + tls.vert_neighbors.reinitialize(verts.size()); + calc_vert_neighbors_interior(verts, tls.vert_neighbors); + const Span> vert_neighbors = tls.vert_neighbors; + + int i = 0; + for (const BMVert *vert : verts) { + if (factors[i] == 0.0f) { + translations[i] = float3(0.0f, 0.0f, 0.0f); + i++; + continue; + } + + /* Don't modify corner vertices */ + if (vert_neighbors[i].size() <= 2) { + translations[i] = float3(0.0f, 0.0f, 0.0f); + i++; + continue; + } + + Vector neighbors; + if (BM_vert_is_boundary(vert)) { + neighbors = filtered_neighbors( + vert_neighbors[i], + relax_face_sets, + [&](const BMVert *vert) { return face_set::vert_has_unique_face_set(vert); }, + [&](const BMVert *vert) { return !BM_vert_is_boundary(vert); }); + } + else { + neighbors = filtered_neighbors( + vert_neighbors[i], + relax_face_sets, + [&](const BMVert *vert) { return face_set::vert_has_unique_face_set(vert); }, + {}); + } + + /* Smoothed position calculation */ + float3 smoothed_position; + const bool has_new_position = get_average_position(neighbors, smoothed_position); + + if (!has_new_position) { + translations[i] = float3(0.0f, 0.0f, 0.0f); + i++; + continue; + } + + /* Normal Calculation */ + float3 normal; + if (BM_vert_is_boundary(vert)) { + bool has_boundary_normal = get_normal_boundary(positions[i], neighbors, normal); + + if (!has_boundary_normal) { + normal = vert->no; + } + } + else { + normal = vert->no; + } + + if (math::is_zero(normal)) { + translations[i] = float3(0.0f, 0.0f, 0.0f); + i++; + continue; + } + + const float3 translation = translation_to_plane(positions[i], normal, smoothed_position); + + translations[i] = translation * factors[i]; + i++; + } +} + +BLI_NOINLINE static void apply_positions_bmesh(const Sculpt &sd, + const Set verts, + Object &object, + const MutableSpan translations, + const Span positions) + +{ + SculptSession &ss = *object.sculpt; + + clip_and_lock_translations(sd, ss, positions, translations); + apply_translations(translations, verts); +} + +static void do_relax_face_sets_brush_bmesh(const Sculpt &sd, + const Brush &brush, + Object &object, + const Span nodes, + const float strength, + const bool relax_face_sets) +{ + Array node_offset_data; + const OffsetIndices node_vert_offsets = create_node_vert_offsets_bmesh(nodes, + node_offset_data); + + Array current_positions(node_vert_offsets.total_size()); + Array translations(node_vert_offsets.total_size()); + Array factors(node_vert_offsets.total_size()); + + threading::EnumerableThreadSpecific all_tls; + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + BMeshLocalData &tls = all_tls.local(); + for (const int i : range) { + calc_factors_bmesh(object, + brush, + *nodes[i], + strength, + relax_face_sets, + tls, + current_positions.as_mutable_span().slice(node_vert_offsets[i]), + factors.as_mutable_span().slice(node_vert_offsets[i])); + } + }); + + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + BMeshLocalData &tls = all_tls.local(); + for (const int i : range) { + calc_relaxed_translations_bmesh( + BKE_pbvh_bmesh_node_unique_verts(nodes[i]), + current_positions.as_mutable_span().slice(node_vert_offsets[i]), + relax_face_sets, + tls, + factors.as_span().slice(node_vert_offsets[i]), + translations.as_mutable_span().slice(node_vert_offsets[i])); + } + }); + + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + apply_positions_bmesh(sd, + BKE_pbvh_bmesh_node_unique_verts(nodes[i]), + object, + translations.as_mutable_span().slice(node_vert_offsets[i]), + current_positions.as_span().slice(node_vert_offsets[i])); + } + }); +} + +} // namespace relax_face_sets_cc + +void do_relax_face_sets_brush(const Sculpt &sd, Object &object, Span nodes) +{ + const Brush &brush = *BKE_paint_brush_for_read(&sd.paint); + + SCULPT_boundary_info_ensure(object); + + const SculptSession &ss = *object.sculpt; + const std::array strengths = iteration_strengths(ss.cache->bstrength, + ss.cache->iteration_count); + + /* On every third step of the stroke, behave more similarly to the Topology Relax brush */ + const bool relax_face_sets = !(ss.cache->iteration_count % 3 == 0); + + for (const float strength : strengths) { + switch (BKE_pbvh_type(*ss.pbvh)) { + case PBVH_FACES: + do_relax_face_sets_brush_mesh( + sd, brush, object, nodes, strength * strength, relax_face_sets); + break; + case PBVH_GRIDS: + do_relax_face_sets_brush_grids( + sd, brush, object, nodes, strength * strength, relax_face_sets); + break; + case PBVH_BMESH: + do_relax_face_sets_brush_bmesh( + sd, brush, object, nodes, strength * strength, relax_face_sets); + 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 88dd8c9bada..956b43dac2e 100644 --- a/source/blender/editors/sculpt_paint/brushes/types.hh +++ b/source/blender/editors/sculpt_paint/brushes/types.hh @@ -41,6 +41,7 @@ void do_mask_brush(const Sculpt &sd, Object &object, Span nodes); void do_multiplane_scrape_brush(const Sculpt &sd, Object &object, Span nodes); void do_pinch_brush(const Sculpt &sd, Object &object, Span nodes); void do_nudge_brush(const Sculpt &sd, Object &object, Span nodes); +void do_relax_face_sets_brush(const Sculpt &sd, Object &object, Span nodes); void do_rotate_brush(const Sculpt &sd, Object &object, Span nodes); void do_scrape_brush(const Sculpt &sd, Object &object, Span nodes); /** Smooth positions with neighboring vertices. */ diff --git a/source/blender/editors/sculpt_paint/mesh_brush_common.hh b/source/blender/editors/sculpt_paint/mesh_brush_common.hh index 41a623fe6bb..f2643052c9d 100644 --- a/source/blender/editors/sculpt_paint/mesh_brush_common.hh +++ b/source/blender/editors/sculpt_paint/mesh_brush_common.hh @@ -13,6 +13,8 @@ #include "BLI_span.hh" #include "BLI_vector.hh" +#include "BKE_subdiv_ccg.hh" + #include "DNA_brush_enums.h" #include "sculpt_intern.hh" @@ -43,6 +45,8 @@ struct PBVHNode; struct Sculpt; struct SculptSession; struct SubdivCCG; +struct SubdivCCGCoord; +struct SubdivCCGNeighbors; namespace blender::ed::sculpt_paint { struct StrokeCache; @@ -318,6 +322,10 @@ void write_translations(const Sculpt &sd, * new array. */ OffsetIndices create_node_vert_offsets(Span nodes, Array &node_data); +OffsetIndices create_node_vert_offsets(Span nodes, + const CCGKey &key, + Array &node_data); +OffsetIndices create_node_vert_offsets_bmesh(Span nodes, Array &node_data); /** * Find vertices connected to the indexed vertices across faces. @@ -354,6 +362,14 @@ void calc_vert_neighbors_interior(OffsetIndices faces, Span hide_poly, Span verts, MutableSpan> result); +void calc_vert_neighbors_interior(OffsetIndices faces, + Span corner_verts, + BitSpan boundary_verts, + const SubdivCCG &subdiv_ccg, + const Span grids, + const MutableSpan> result); +void calc_vert_neighbors_interior(const Set &verts, + MutableSpan> result); /** Find the translation from each vertex position to the closest point on the plane. */ void calc_translations_to_plane(Span vert_positions, diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index 9abc206e992..7b160649eef 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -4047,7 +4047,7 @@ static void do_brush_action(const Scene &scene, do_draw_face_sets_brush(sd, ob, nodes); } else { - face_set::do_relax_face_sets_brush(sd, ob, nodes); + do_relax_face_sets_brush(sd, ob, nodes); } break; case SCULPT_TOOL_DISPLACEMENT_ERASER: @@ -7480,6 +7480,26 @@ OffsetIndices create_node_vert_offsets(Span nodes, Array & return offset_indices::accumulate_counts_to_offsets(node_data); } +OffsetIndices create_node_vert_offsets(Span nodes, + const CCGKey &key, + Array &node_data) +{ + node_data.reinitialize(nodes.size() + 1); + for (const int i : nodes.index_range()) { + node_data[i] = bke::pbvh::node_grid_indices(*nodes[i]).size() * key.grid_area; + } + return offset_indices::accumulate_counts_to_offsets(node_data); +} + +OffsetIndices create_node_vert_offsets_bmesh(Span nodes, Array &node_data) +{ + node_data.reinitialize(nodes.size() + 1); + for (const int i : nodes.index_range()) { + node_data[i] = BKE_pbvh_bmesh_node_unique_verts(nodes[i]).size(); + } + return offset_indices::accumulate_counts_to_offsets(node_data); +} + void calc_vert_neighbors(const OffsetIndices faces, const Span corner_verts, const GroupedSpan vert_to_face, @@ -7546,6 +7566,72 @@ void calc_vert_neighbors_interior(const OffsetIndices faces, } } +void calc_vert_neighbors_interior(const OffsetIndices faces, + const Span corner_verts, + const BitSpan boundary_verts, + const SubdivCCG &subdiv_ccg, + const Span grids, + const MutableSpan> result) +{ + const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg); + + BLI_assert(grids.size() * key.grid_area == result.size()); + + for (const int i : grids.index_range()) { + const int grid = grids[i]; + const int node_verts_start = i * key.grid_area; + + /* TODO: This loop could be optimized in the future by skipping unnecessary logic for + * non-boundary grid vertices. */ + 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_verts_start + offset; + + 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); + + if (BKE_subdiv_ccg_coord_is_mesh_boundary( + faces, corner_verts, boundary_verts, subdiv_ccg, coord)) + { + if (neighbors.coords.size() == 2) { + /* Do not include neighbors of corner vertices. */ + neighbors.coords.clear(); + } + else { + /* Only include other boundary vertices as neighbors of boundary vertices. */ + neighbors.coords.remove_if([&](const SubdivCCGCoord coord) { + return !BKE_subdiv_ccg_coord_is_mesh_boundary( + faces, corner_verts, boundary_verts, subdiv_ccg, coord); + }); + } + } + result[node_vert_index] = neighbors.coords; + } + } + } +} + +void calc_vert_neighbors_interior(const Set &verts, + MutableSpan> result) +{ + BLI_assert(verts.size() == result.size()); + Vector neighbor_data; + + int i = 0; + for (BMVert *vert : verts) { + neighbor_data.clear(); + vert_neighbors_get_interior_bmesh(*vert, neighbor_data); + result[i] = neighbor_data; + i++; + } +} + void calc_translations_to_plane(const Span vert_positions, const Span verts, const float4 &plane, diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.cc b/source/blender/editors/sculpt_paint/sculpt_face_set.cc index c5d019b2a77..464309119b5 100644 --- a/source/blender/editors/sculpt_paint/sculpt_face_set.cc +++ b/source/blender/editors/sculpt_paint/sculpt_face_set.cc @@ -207,89 +207,6 @@ Array duplicate_face_sets(const Mesh &mesh) /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Draw Face Sets Brush - * \{ */ - -static void do_relax_face_sets_brush_task(Object &ob, - const Brush &brush, - const float strength, - PBVHNode *node) -{ - SculptSession &ss = *ob.sculpt; - - PBVHVertexIter vd; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, test, brush.falloff_shape); - - const bool relax_face_sets = !(ss.cache->iteration_count % 3 == 0); - - 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) { - auto_mask::node_update(automask_data, vd); - - if (!sculpt_brush_test_sq_fn(test, vd.co)) { - continue; - } - if (relax_face_sets == vert_has_unique_face_set(ss, vd.vertex)) { - continue; - } - - 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); - - smooth::relax_vertex(ss, &vd, fade * strength * strength, relax_face_sets, vd.co); - } - BKE_pbvh_vertex_iter_end; -} - -static std::array iteration_strengths(const float strength, const int stroke_iteration) -{ - if (stroke_iteration % 3 == 0) { - return {strength, strength, strength, strength}; - } - - /* This operations needs a strength tweak as the relax deformation is too weak by default. */ - const float modified_strength = strength * 1.5f; - return {modified_strength, modified_strength, strength, strength}; -} - -void do_relax_face_sets_brush(const Sculpt &sd, Object &ob, Span nodes) -{ - const Brush &brush = *BKE_paint_brush_for_read(&sd.paint); - - BKE_curvemapping_init(brush.curve); - - SCULPT_boundary_info_ensure(ob); - - const SculptSession &ss = *ob.sculpt; - const std::array strengths = iteration_strengths(ss.cache->bstrength, - ss.cache->iteration_count); - for (const float strength : strengths) { - - threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { - for (const int i : range) { - do_relax_face_sets_brush_task(ob, brush, strength, nodes[i]); - } - }); - } -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Global Mesh Operators * Operators that work on the mesh as a whole. diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.hh b/source/blender/editors/sculpt_paint/sculpt_intern.hh index 161bc6f064a..367b35da5c7 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/sculpt_intern.hh @@ -2003,12 +2003,6 @@ void multiplane_scrape_preview_draw(uint gpuattr, const float outline_col[3], float outline_alpha); -namespace face_set { - -void do_relax_face_sets_brush(const Sculpt &sd, Object &ob, Span nodes); - -} - namespace color { /* Swaps colors at each element in indices with values in colors. */