From 4f73bf2871e2312c5afbc0eaccc8bb34f0fd4e96 Mon Sep 17 00:00:00 2001 From: Sean Kim Date: Thu, 11 Jul 2024 20:55:03 +0200 Subject: [PATCH] Sculpt: Initial data oriented refactor of relax face set brush Part of #118145. For each of the PBVH types, the refactored Relax Face Set brush performs the following steps: * Calculate all relevant element factors like other refactored brushes * Calculate the displacement by performing the following steps, using a displacement of 0 if certain conditions are unfulfilled (e.g. factor will have no effect, no relevant neighbor vertices, etc) * Find all neighbors for each element * Filter the neighbors based on if the current vert is or is not a boundary vert, looking at the face sets of each neighbor * From the relevant neighbors, calculate a smoothed average position. * For boundary verts, calculate the normal based on the summed direction towards each neighbor vertex * For non-boundary verts, retrieve the current vertex normal. * Use the current position and the retrieved normal to create a plane. * Find the closest point on the plane to the smoothed position, use this as the final displacement * Apply all of these displacements to the relevant elements Q: Why do we perform three different parallel loops for each method instead of a single one like other brushes? Because much of the processing time for these functions is spent in the neighbor calculation, we opt to process factors in an earlier loop to short-circuit neighbor calculation if we know it cannot possibly have an effect (i.e `factor[i] == 0.0f`) Q: Why are lambdas used in `filtered_neighbors` ? Simply put, reducing the overall amount of code duplication, otherwise this function would require 6 different methods to do the necessary filtering (boundary vs non boundary vertices for each of the three pbvh types) ### Potential Areas of Improvement * Unique face set calculation requires iterating over the `vert_to_face` map and happens multiple times per brush stroke (each stroke performs the vertex tasks 4 times in succession). Some form of cache or precomputed map similar to the `boundary_verts` `BitSpan` may improve performance. Pull Request: https://projects.blender.org/blender/blender/pulls/124135 --- .../editors/sculpt_paint/CMakeLists.txt | 1 + .../sculpt_paint/brushes/relax_face_sets.cc | 1033 +++++++++++++++++ .../editors/sculpt_paint/brushes/types.hh | 1 + .../editors/sculpt_paint/mesh_brush_common.hh | 16 + source/blender/editors/sculpt_paint/sculpt.cc | 88 +- .../editors/sculpt_paint/sculpt_face_set.cc | 83 -- .../editors/sculpt_paint/sculpt_intern.hh | 6 - 7 files changed, 1138 insertions(+), 90 deletions(-) create mode 100644 source/blender/editors/sculpt_paint/brushes/relax_face_sets.cc 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. */