Fix #132520: Pose Brush "Use Connected" behaves oddly

When the brush refactor project was in progress, the "Fake Neighbors"
feature used exclusively by the Pose brush was made more explicit.
However, the migration missed the usage of this data in the `flood_fill`
APIs.

This resulted in the option not affected unconnected topology islands
except when the Pose Origin Offset was non zero and using the Topology
mode of the brush.

To fix this, this commit updates the flood_fill APIs to take in the
pre-calculated fake neighbor data and use it when traversing connected
vertices and applying weights.

Pull Request: https://projects.blender.org/blender/blender/pulls/132610
This commit is contained in:
Sean Kim
2025-01-06 21:38:36 +01:00
committed by Sean Kim
parent a4bfa399e9
commit 72180241c1
3 changed files with 53 additions and 17 deletions

View File

@@ -93,9 +93,12 @@ void FillDataMesh::execute(Object &object,
const int from_v = this->queue.front();
this->queue.pop();
for (const int neighbor : vert_neighbors_get_mesh(
faces, corner_verts, vert_to_face_map, hide_poly, from_v, neighbors))
{
vert_neighbors_get_mesh(faces, corner_verts, vert_to_face_map, hide_poly, from_v, neighbors);
if (!this->fake_neighbors.is_empty() && this->fake_neighbors[from_v] != FAKE_NEIGHBOR_NONE) {
neighbors.append(this->fake_neighbors[from_v]);
}
for (const int neighbor : neighbors) {
if (this->visited_verts[neighbor]) {
continue;
}
@@ -124,6 +127,13 @@ void FillDataGrids::execute(
SubdivCCGNeighbors neighbors;
BKE_subdiv_ccg_neighbor_coords_get(subdiv_ccg, from_v, true, neighbors);
if (!this->fake_neighbors.is_empty() &&
this->fake_neighbors[from_v.to_index(key)] != FAKE_NEIGHBOR_NONE)
{
neighbors.coords.insert(
0, SubdivCCGCoord::from_index(key, this->fake_neighbors[from_v.to_index(key)]));
}
const int num_unique = neighbors.coords.size() - neighbors.num_duplicates;
/* Flood fill expects the duplicate entries to be passed to the per-neighbor lambda first, so
@@ -151,14 +161,20 @@ void FillDataGrids::execute(
}
}
void FillDataBMesh::execute(Object & /*object*/,
FunctionRef<bool(BMVert *from_v, BMVert *to_v)> func)
void FillDataBMesh::execute(Object &object, FunctionRef<bool(BMVert *from_v, BMVert *to_v)> func)
{
BMesh *bm = object.sculpt->bm;
Vector<BMVert *, 64> neighbors;
while (!this->queue.empty()) {
BMVert *from_v = this->queue.front();
this->queue.pop();
if (!this->fake_neighbors.is_empty() &&
this->fake_neighbors[BM_elem_index_get(from_v)] != FAKE_NEIGHBOR_NONE)
{
neighbors.append(BM_vert_at_index(bm, this->fake_neighbors[BM_elem_index_get(from_v)]));
}
for (BMVert *neighbor : vert_neighbors_get_bmesh(*from_v, neighbors)) {
const int neighbor_idx = BM_elem_index_get(neighbor);
if (this->visited_verts[neighbor_idx]) {

View File

@@ -26,8 +26,14 @@ namespace blender::ed::sculpt_paint::flood_fill {
struct FillDataMesh {
std::queue<int> queue;
BitVector<> visited_verts;
Span<int> fake_neighbors;
FillDataMesh(int size) : visited_verts(size) {}
FillDataMesh(int size, Span<int> fake_neighbors)
: visited_verts(size), fake_neighbors(fake_neighbors)
{
BLI_assert(fake_neighbors.is_empty() || size == fake_neighbors.size());
}
void add_initial(int vertex);
void add_initial(Span<int> verts);
@@ -40,8 +46,14 @@ struct FillDataMesh {
struct FillDataGrids {
std::queue<SubdivCCGCoord> queue;
BitVector<> visited_verts;
Span<int> fake_neighbors;
FillDataGrids(int size) : visited_verts(size) {}
FillDataGrids(int size, Span<int> fake_neighbors)
: visited_verts(size), fake_neighbors(fake_neighbors)
{
BLI_assert(fake_neighbors.is_empty() || size == fake_neighbors.size());
}
void add_initial(SubdivCCGCoord vertex);
void add_initial(const CCGKey &key, Span<int> verts);
@@ -55,8 +67,14 @@ struct FillDataGrids {
struct FillDataBMesh {
std::queue<BMVert *> queue;
BitVector<> visited_verts;
Span<int> fake_neighbors;
FillDataBMesh(int size) : visited_verts(size) {}
FillDataBMesh(int size, Span<int> fake_neighbors)
: visited_verts(size), fake_neighbors(fake_neighbors)
{
BLI_assert(fake_neighbors.is_empty() || size == fake_neighbors.size());
}
void add_initial(BMVert *vertex);
void add_initial(BMesh &bm, Span<int> verts);

View File

@@ -679,7 +679,7 @@ static void calc_pose_origin_and_factor_mesh(const Depsgraph &depsgraph,
const Span<float3> positions_eval = bke::pbvh::vert_positions_eval(depsgraph, object);
/* Calculate the pose rotation point based on the boundaries of the brush factor. */
flood_fill::FillDataMesh flood(positions_eval.size());
flood_fill::FillDataMesh flood(positions_eval.size(), ss.fake_neighbors.fake_neighbor_index);
flood.add_initial(find_symm_verts_mesh(depsgraph, object, ss.active_vert_index(), radius));
const int symm = SCULPT_mesh_symmetry_xyz_get(object);
@@ -731,7 +731,7 @@ static void calc_pose_origin_and_factor_grids(Object &object,
const Span<float3> positions = subdiv_ccg.positions;
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
/* Calculate the pose rotation point based on the boundaries of the brush factor. */
flood_fill::FillDataGrids flood(positions.size());
flood_fill::FillDataGrids flood(positions.size(), ss.fake_neighbors.fake_neighbor_index);
flood.add_initial(key, find_symm_verts_grids(object, ss.active_vert_index(), radius));
const int symm = SCULPT_mesh_symmetry_xyz_get(object);
@@ -785,7 +785,8 @@ static void calc_pose_origin_and_factor_bmesh(Object &object,
SCULPT_vertex_random_access_ensure(object);
/* Calculate the pose rotation point based on the boundaries of the brush factor. */
flood_fill::FillDataBMesh flood(BM_mesh_elem_count(ss.bm, BM_VERT));
flood_fill::FillDataBMesh flood(BM_mesh_elem_count(ss.bm, BM_VERT),
ss.fake_neighbors.fake_neighbor_index);
flood.add_initial(*ss.bm, find_symm_verts_bmesh(object, ss.active_vert_index(), radius));
const int symm = SCULPT_mesh_symmetry_xyz_get(object);
@@ -1055,7 +1056,8 @@ static std::unique_ptr<IKChain> ik_chain_init_face_sets_mesh(const Depsgraph &de
for (const int i : ik_chain->segments.index_range()) {
const bool is_first_iteration = i == 0;
flood_fill::FillDataMesh flood_fill(vert_positions.size());
flood_fill::FillDataMesh flood_fill(vert_positions.size(),
ss.fake_neighbors.fake_neighbor_index);
flood_fill.add_initial(find_symm_verts_mesh(depsgraph, object, current_data.vert, radius));
visited_face_sets.add(current_data.face_set);
@@ -1215,7 +1217,7 @@ static std::unique_ptr<IKChain> ik_chain_init_face_sets_grids(Object &object,
for (const int i : ik_chain->segments.index_range()) {
const bool is_first_iteration = i == 0;
flood_fill::FillDataGrids flood_fill(grids_num);
flood_fill::FillDataGrids flood_fill(grids_num, ss.fake_neighbors.fake_neighbor_index);
flood_fill.add_initial(key, find_symm_verts_grids(object, current_data.vert, radius));
visited_face_sets.add(current_data.face_set);
@@ -1374,7 +1376,7 @@ static std::unique_ptr<IKChain> ik_chain_init_face_sets_bmesh(Object &object,
for (const int i : ik_chain->segments.index_range()) {
const bool is_first_iteration = i == 0;
flood_fill::FillDataBMesh flood_fill(verts_num);
flood_fill::FillDataBMesh flood_fill(verts_num, ss.fake_neighbors.fake_neighbor_index);
flood_fill.add_initial(
*ss.bm, find_symm_verts_bmesh(object, BM_elem_index_get(current_data.vert), radius));
@@ -1619,7 +1621,7 @@ static std::unique_ptr<IKChain> ik_chain_init_face_sets_fk_mesh(const Depsgraph
int masked_face_set = SCULPT_FACE_SET_NONE;
int target_face_set = SCULPT_FACE_SET_NONE;
int masked_face_set_it = 0;
flood_fill::FillDataMesh step_floodfill(mesh.verts_num);
flood_fill::FillDataMesh step_floodfill(mesh.verts_num, ss.fake_neighbors.fake_neighbor_index);
step_floodfill.add_initial(active_vert);
step_floodfill.execute(object, vert_to_face_map, [&](int from_v, int to_v) {
floodfill_step[to_v] = floodfill_step[from_v] + 1;
@@ -1660,7 +1662,7 @@ static std::unique_ptr<IKChain> ik_chain_init_face_sets_fk_mesh(const Depsgraph
ik_chain->segments[0].head = head.value_or(initial_location);
ik_chain->grab_delta_offset = ik_chain->segments[0].head - initial_location;
flood_fill::FillDataMesh weight_floodfill(mesh.verts_num);
flood_fill::FillDataMesh weight_floodfill(mesh.verts_num, ss.fake_neighbors.fake_neighbor_index);
weight_floodfill.add_initial(find_symm_verts_mesh(depsgraph, object, active_vert, radius));
MutableSpan<float> fk_weights = ik_chain->segments[0].weights;
weight_floodfill.execute(object, vert_to_face_map, [&](int /*from_v*/, int to_v) {
@@ -1704,7 +1706,7 @@ static std::unique_ptr<IKChain> ik_chain_init_face_sets_fk_grids(const Depsgraph
int masked_face_set = SCULPT_FACE_SET_NONE;
int target_face_set = SCULPT_FACE_SET_NONE;
int masked_face_set_it = 0;
flood_fill::FillDataGrids step_floodfill(grids_num);
flood_fill::FillDataGrids step_floodfill(grids_num, ss.fake_neighbors.fake_neighbor_index);
step_floodfill.add_initial(SubdivCCGCoord::from_index(key, active_vert_index));
step_floodfill.execute(
object, subdiv_ccg, [&](SubdivCCGCoord from_v, SubdivCCGCoord to_v, bool is_duplicate) {
@@ -1757,7 +1759,7 @@ static std::unique_ptr<IKChain> ik_chain_init_face_sets_fk_grids(const Depsgraph
ik_chain->segments[0].head = head.value_or(initial_location);
ik_chain->grab_delta_offset = ik_chain->segments[0].head - initial_location;
flood_fill::FillDataGrids weight_floodfill(grids_num);
flood_fill::FillDataGrids weight_floodfill(grids_num, ss.fake_neighbors.fake_neighbor_index);
weight_floodfill.add_initial(key, find_symm_verts_grids(object, active_vert_index, radius));
MutableSpan<float> fk_weights = ik_chain->segments[0].weights;
weight_floodfill.execute(
@@ -1802,7 +1804,7 @@ static std::unique_ptr<IKChain> ik_chain_init_face_sets_fk_bmesh(const Depsgraph
int masked_face_set = SCULPT_FACE_SET_NONE;
int target_face_set = SCULPT_FACE_SET_NONE;
int masked_face_set_it = 0;
flood_fill::FillDataBMesh step_floodfill(verts_num);
flood_fill::FillDataBMesh step_floodfill(verts_num, ss.fake_neighbors.fake_neighbor_index);
step_floodfill.add_initial(active_vert);
step_floodfill.execute(object, [&](BMVert *from_v, BMVert *to_v) {
const int from_v_i = BM_elem_index_get(from_v);
@@ -1846,7 +1848,7 @@ static std::unique_ptr<IKChain> ik_chain_init_face_sets_fk_bmesh(const Depsgraph
ik_chain->segments[0].head = head.value_or(initial_location);
ik_chain->grab_delta_offset = ik_chain->segments[0].head - initial_location;
flood_fill::FillDataBMesh weight_floodfill(verts_num);
flood_fill::FillDataBMesh weight_floodfill(verts_num, ss.fake_neighbors.fake_neighbor_index);
weight_floodfill.add_initial(
*ss.bm, find_symm_verts_bmesh(object, BM_elem_index_get(active_vert), radius));
MutableSpan<float> fk_weights = ik_chain->segments[0].weights;