Sculpt: Add increase / decrease visibility operator #120282
|
@ -5609,6 +5609,10 @@ def km_sculpt(params):
|
|||
{"properties": [("mode", 'HIDE_ACTIVE')]}),
|
||||
("paint.hide_show_all", {"type": 'H', "value": 'PRESS', "alt": True},
|
||||
{"properties": [("action", "SHOW")]}),
|
||||
("paint.visibility_filter", {"type": 'PAGE_UP', "value": 'PRESS', "repeat": True},
|
||||
{"properties": [("action", 'GROW')]}),
|
||||
("paint.visibility_filter", {"type": 'PAGE_DOWN', "value": 'PRESS', "repeat": True},
|
||||
{"properties": [("action", 'SHRINK')]}),
|
||||
("sculpt.face_set_edit", {"type": 'W', "value": 'PRESS', "ctrl": True},
|
||||
{"properties": [("mode", 'GROW')]}),
|
||||
("sculpt.face_set_edit", {"type": 'W', "value": 'PRESS', "ctrl": True, "alt": True},
|
||||
|
|
|
@ -3818,6 +3818,12 @@ class VIEW3D_MT_sculpt(Menu):
|
|||
props = layout.operator("paint.hide_show_masked", text="Hide Masked")
|
||||
props.action = 'HIDE'
|
||||
|
||||
props = layout.operator("paint.visibility_filter", text="Grow Visibility")
|
||||
props.action = "GROW"
|
||||
|
||||
props = layout.operator("paint.visibility_filter", text="Shrink Visibility")
|
||||
props.action = "SHRINK"
|
||||
|
||||
layout.separator()
|
||||
|
||||
props = layout.operator("sculpt.trim_box_gesture", text="Box Trim")
|
||||
|
|
|
@ -343,6 +343,12 @@ void mesh_sharp_edges_set_from_angle(Mesh &mesh, float angle, bool keep_sharp_ed
|
|||
* vertices are hidden. */
|
||||
void mesh_edge_hide_from_vert(Span<int2> edges, Span<bool> hide_vert, MutableSpan<bool> hide_edge);
|
||||
|
||||
/* Hide faces when any of their vertices are hidden. */
|
||||
void mesh_face_hide_from_vert(OffsetIndices<int> faces,
|
||||
Span<int> corner_verts,
|
||||
Span<bool> hide_vert,
|
||||
MutableSpan<bool> hide_poly);
|
||||
|
||||
/** Make edge and face visibility consistent with vertices. */
|
||||
void mesh_hide_vert_flush(Mesh &mesh);
|
||||
/** Make vertex and edge visibility consistent with faces. */
|
||||
|
|
|
@ -517,11 +517,10 @@ void mesh_edge_hide_from_vert(const Span<int2> edges,
|
|||
});
|
||||
}
|
||||
|
||||
/* Hide faces when any of their vertices are hidden. */
|
||||
static void face_hide_from_vert(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const Span<bool> hide_vert,
|
||||
MutableSpan<bool> hide_poly)
|
||||
void mesh_face_hide_from_vert(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const Span<bool> hide_vert,
|
||||
MutableSpan<bool> hide_poly)
|
||||
{
|
||||
using namespace blender;
|
||||
threading::parallel_for(faces.index_range(), 4096, [&](const IndexRange range) {
|
||||
|
@ -552,7 +551,7 @@ void mesh_hide_vert_flush(Mesh &mesh)
|
|||
".hide_poly", AttrDomain::Face);
|
||||
|
||||
mesh_edge_hide_from_vert(mesh.edges(), hide_vert_span, hide_edge.span);
|
||||
face_hide_from_vert(mesh.faces(), mesh.corner_verts(), hide_vert_span, hide_poly.span);
|
||||
mesh_face_hide_from_vert(mesh.faces(), mesh.corner_verts(), hide_vert_span, hide_poly.span);
|
||||
|
||||
hide_edge.finish();
|
||||
hide_poly.finish();
|
||||
|
|
|
@ -192,7 +192,7 @@ enum class VisAction {
|
|||
Show = 1,
|
||||
};
|
||||
|
||||
static bool action_to_hide(const VisAction action)
|
||||
constexpr static bool action_to_hide(const VisAction action)
|
||||
{
|
||||
return action == VisAction::Hide;
|
||||
}
|
||||
|
@ -253,6 +253,18 @@ static void flush_face_changes_node(Mesh &mesh,
|
|||
hide_poly.finish();
|
||||
}
|
||||
|
||||
/* Updates a node's face's visibility based on the updated vertex visibility. */
|
||||
static void flush_face_changes(Mesh &mesh, const Span<bool> hide_vert)
|
||||
{
|
||||
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
|
||||
bke::SpanAttributeWriter<bool> hide_poly = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".hide_poly", bke::AttrDomain::Face);
|
||||
|
||||
bke::mesh_face_hide_from_vert(mesh.faces(), mesh.corner_verts(), hide_vert, hide_poly.span);
|
||||
hide_poly.finish();
|
||||
}
|
||||
|
||||
/* Updates all of a mesh's edge visibility based on vertex visibility. */
|
||||
static void flush_edge_changes(Mesh &mesh, const Span<bool> hide_vert)
|
||||
{
|
||||
|
@ -787,6 +799,449 @@ void PAINT_OT_visibility_invert(wmOperatorType *ot)
|
|||
ot->flag = OPTYPE_REGISTER;
|
||||
}
|
||||
|
||||
/* Number of vertices per iteration step size when growing or shrinking visibility. */
|
||||
static constexpr float VERTEX_ITERATION_THRESHOLD = 50000.0f;
|
||||
|
||||
/* For both this and the following #shrinK_face_visibility_mesh methods, extracting the loop
|
||||
* and comparing against / writing with a constant `false` or `true` instead of using
|
||||
* #action_to_hide results in a nearly 600ms speedup on a mesh with 1.5m verts. */
|
||||
static void grow_face_visibility_mesh(const IndexRange &face,
|
||||
Sean-Kim marked this conversation as resolved
Outdated
|
||||
const Span<int> corner_verts,
|
||||
const Span<bool> read_buffer,
|
||||
MutableSpan<bool> write_buffer)
|
||||
{
|
||||
for (const int corner : face) {
|
||||
int vert = corner_verts[corner];
|
||||
if (read_buffer[vert]) {
|
||||
Sean-Kim marked this conversation as resolved
Outdated
Hans Goudey
commented
Should probably use Should probably use `lookup_or_default` here in case the hide_poly attribute doesn't exist for some reason.
|
||||
continue;
|
||||
}
|
||||
|
||||
const int prev = bke::mesh::face_corner_prev(face, corner);
|
||||
const int prev_vert = corner_verts[prev];
|
||||
write_buffer[prev_vert] = false;
|
||||
|
||||
const int next = bke::mesh::face_corner_next(face, corner);
|
||||
const int next_vert = corner_verts[next];
|
||||
write_buffer[next_vert] = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void shrink_face_visibility_mesh(const IndexRange &face,
|
||||
Hans Goudey
commented
I think this will allow deduplicating these two functions:
Also I think this will allow deduplicating these two functions:
```cpp
template<bool value>
static void affect_visibility_mesh(const IndexRange &face,
const Span<int> corner_verts,
const Span<bool> read_buffer,
MutableSpan<bool> write_buffer)
{
for (const int corner : face) {
int vert = corner_verts[corner];
if (read_buffer[vert] != value) {
continue;
}
const int prev = bke::mesh::face_corner_prev(face, corner);
const int prev_vert = corner_verts[prev];
write_buffer[prev_vert] = value;
const int next = bke::mesh::face_corner_next(face, corner);
const int next_vert = corner_verts[next];
write_buffer[next_vert] = value;
}
}
```
Also `IndexRange` is a small struct and can be passed by value. I usually just pass a sliced `corner_verts` already though, and call it `face_verts`
Sean Kim
commented
Isn't Isn't `IndexRange face` still needed for `face_corner_prev` & `face_corner_next`?
|
||||
const Span<int> corner_verts,
|
||||
const Span<bool> read_buffer,
|
||||
Sean-Kim marked this conversation as resolved
Outdated
Hans Goudey
commented
Could iterate over the indices instead of their index range. Same for multires Could iterate over the indices instead of their index range. Same for multires
|
||||
MutableSpan<bool> write_buffer)
|
||||
{
|
||||
for (const int corner : face) {
|
||||
int vert = corner_verts[corner];
|
||||
if (!read_buffer[vert]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int prev = bke::mesh::face_corner_prev(face, corner);
|
||||
const int prev_vert = corner_verts[prev];
|
||||
write_buffer[prev_vert] = true;
|
||||
|
||||
const int next = bke::mesh::face_corner_next(face, corner);
|
||||
const int next_vert = corner_verts[next];
|
||||
write_buffer[next_vert] = true;
|
||||
}
|
||||
}
|
||||
|
||||
struct DualBuffer {
|
||||
Array<bool> front;
|
||||
Array<bool> back;
|
||||
|
||||
MutableSpan<bool> write_buffer(int count)
|
||||
{
|
||||
return count % 2 == 0 ? back.as_mutable_span() : front.as_mutable_span();
|
||||
}
|
||||
|
||||
Span<bool> read_buffer(int count)
|
||||
{
|
||||
return count % 2 == 0 ? front.as_span() : back.as_span();
|
||||
}
|
||||
};
|
||||
|
||||
static int propagate_vertex_visibility(Mesh &mesh,
|
||||
DualBuffer &buffers,
|
||||
const VArraySpan<bool> &hide_poly,
|
||||
const VisAction action,
|
||||
const int iterations)
|
||||
{
|
||||
const OffsetIndices faces = mesh.faces();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
|
||||
int last_changed_iteration = -1;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
Span<bool> read_buffer = buffers.read_buffer(i);
|
||||
MutableSpan<bool> write_buffer = buffers.write_buffer(i);
|
||||
threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
|
||||
Sean-Kim marked this conversation as resolved
Outdated
Hans Goudey
commented
Some of the same performance concerns apply here from the mesh case, particularly copying the grid visibility every iteration. Some of the same performance concerns apply here from the mesh case, particularly copying the grid visibility every iteration.
|
||||
for (const int face_index : range) {
|
||||
Sean-Kim marked this conversation as resolved
Outdated
Hans Goudey
commented
`BitGroupVector` should probably have a copy constructor instead of this manual copy of each group individually
Sean Kim
commented
I'll pull this out into a separate PR. I'll pull this out into a separate PR.
Sean Kim
commented
PR: #122026 PR: #122026
|
||||
if (!hide_poly[face_index]) {
|
||||
continue;
|
||||
}
|
||||
const IndexRange face = faces[face_index];
|
||||
if (action == VisAction::Hide) {
|
||||
shrink_face_visibility_mesh(face, corner_verts, read_buffer, write_buffer);
|
||||
}
|
||||
else {
|
||||
grow_face_visibility_mesh(face, corner_verts, read_buffer, write_buffer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const bool any_changed = read_buffer != write_buffer;
|
||||
Hans Goudey
commented
How about this?
Also did you find that this was occurring frequently? I wouldn't imagine it would be that common-- maybe it's not worth it. How about this?
```cpp
if (read_buffer == write_buffer) {
/* If nothing has changed for a given iteration, nothing will change in future iterations. */
break;
}
flush_face_changes(mesh, write_buffer);
last_changed_iteration = i;
```
Also did you find that this was occurring frequently? I wouldn't imagine it would be that common-- maybe it's not worth it.
Sean Kim
commented
Realistically, it only happens when you're hiding a mesh that's already mostly hidden or showing a mesh that's already mostly visible. I didn't measure the impact of removing this and just letting the iteration go further Realistically, it only happens when you're hiding a mesh that's already mostly hidden or showing a mesh that's already mostly visible. I didn't measure the impact of removing this and just letting the iteration go further
|
||||
if (any_changed) {
|
||||
flush_face_changes(mesh, write_buffer);
|
||||
last_changed_iteration = i;
|
||||
}
|
||||
else {
|
||||
/* If nothing has changed for a given iteration, nothing will change in future iterations. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return last_changed_iteration;
|
||||
}
|
||||
|
||||
static void update_undo_state(Object &object,
|
||||
const Span<PBVHNode *> nodes,
|
||||
const Span<bool> old_hide_vert,
|
||||
const Span<bool> new_hide_vert)
|
||||
{
|
||||
threading::EnumerableThreadSpecific<Vector<bool>> all_new_hide;
|
||||
Sean-Kim marked this conversation as resolved
Outdated
Hans Goudey
commented
Could you split this logic to a separate local function called Could you split this logic to a separate local function called `elem_xy_to_index`?
Hans Goudey
commented
I like the idea of reusing the abstractions from elsewhere here, but maybe it's not totally worth it? For example this is quite a bit simpler:
Though actually, I think the only reason not to use I like the idea of reusing the abstractions from elsewhere here, but maybe it's not totally worth it? For example this is quite a bit simpler:
```cpp
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
for (const int vert : bke::pbvh::node_unique_verts(*node)) {
if (old_hide_vert[vert] != new_hide_vert[vert]) {
undo::push_node(object, node, undo::Type::HideVert);
break;
}
}
}
});
```
Though actually, I think the only reason not to use `vert_hide_update` instead is an extra step of vert->face flushing. Maybe if that was resolved we could benefit from reusing that code?
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
Vector<bool> &new_hide = all_new_hide.local();
|
||||
for (PBVHNode *node : nodes.slice(range)) {
|
||||
const Span<int> verts = bke::pbvh::node_unique_verts(*node);
|
||||
|
||||
new_hide.reinitialize(verts.size());
|
||||
array_utils::gather(new_hide_vert, verts, new_hide.as_mutable_span());
|
||||
if (array_utils::indexed_data_equal<bool>(old_hide_vert, verts, new_hide)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
undo::push_node(object, node, undo::Type::HideVert);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void update_node_visibility_from_face_changes(const Span<PBVHNode *> nodes,
|
||||
const Span<int> tri_faces,
|
||||
const Span<bool> orig_hide_poly,
|
||||
const Span<bool> new_hide_poly,
|
||||
const Span<bool> hide_vert)
|
||||
{
|
||||
threading::EnumerableThreadSpecific<Vector<int>> all_face_indices;
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
Vector<int> &face_indices = all_face_indices.local();
|
||||
for (PBVHNode *node : nodes.slice(range)) {
|
||||
bool any_changed = false;
|
||||
const Span<int> indices = bke::pbvh::node_face_indices_calc_mesh(
|
||||
tri_faces, *node, face_indices);
|
||||
for (const int face_index : indices) {
|
||||
if (orig_hide_poly[face_index] != new_hide_poly[face_index]) {
|
||||
any_changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (any_changed) {
|
||||
BKE_pbvh_node_mark_update_visibility(node);
|
||||
bke::pbvh::node_update_visibility_mesh(hide_vert, *node);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void grow_shrink_visibility_mesh(Object &object,
|
||||
const Span<PBVHNode *> nodes,
|
||||
const VisAction action,
|
||||
const int iterations)
|
||||
{
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
if (!attributes.contains(".hide_vert")) {
|
||||
/* If the entire mesh is visible, we can neither grow nor shrink the boundary. */
|
||||
return;
|
||||
}
|
||||
|
||||
bke::SpanAttributeWriter<bool> hide_vert = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".hide_vert", bke::AttrDomain::Point);
|
||||
const VArraySpan hide_poly = *attributes.lookup_or_default<bool>(
|
||||
".hide_poly", bke::AttrDomain::Face, false);
|
||||
|
||||
DualBuffer buffers;
|
||||
buffers.back.reinitialize(hide_vert.span.size());
|
||||
buffers.front.reinitialize(hide_vert.span.size());
|
||||
array_utils::copy(hide_vert.span.as_span(), buffers.back.as_mutable_span());
|
||||
array_utils::copy(hide_vert.span.as_span(), buffers.front.as_mutable_span());
|
||||
|
||||
Vector<bool> orig_hide_poly(hide_poly);
|
||||
Sean-Kim marked this conversation as resolved
Hans Goudey
commented
`Vector` -> `Array` here since it isn't resized
|
||||
const int last_changed_iteration = propagate_vertex_visibility(
|
||||
mesh, buffers, hide_poly, action, iterations);
|
||||
if (last_changed_iteration == -1) {
|
||||
/* No changes at all happened. */
|
||||
hide_vert.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
const Span<bool> last_buffer = buffers.write_buffer(last_changed_iteration);
|
||||
|
||||
update_undo_state(object, nodes, hide_vert.span, last_buffer);
|
||||
|
||||
/* We can wait until after all iterations are done to flush edge changes as they are
|
||||
* not used for coarse filtering while iterating.*/
|
||||
flush_edge_changes(mesh, last_buffer);
|
||||
|
||||
update_node_visibility_from_face_changes(
|
||||
nodes, mesh.corner_tri_faces(), orig_hide_poly, hide_poly, last_buffer);
|
||||
array_utils::copy(last_buffer, hide_vert.span);
|
||||
hide_vert.finish();
|
||||
}
|
||||
|
||||
/* TODO: This is probably better off as a member function of a CCGKey?*/
|
||||
static int elem_xy_to_index(int x, int y, int grid_size)
|
||||
{
|
||||
return y * grid_size + x;
|
||||
}
|
||||
|
||||
struct DualBitBuffer {
|
||||
BitGroupVector<> front;
|
||||
BitGroupVector<> back;
|
||||
|
||||
BitGroupVector<> &write_buffer(int count)
|
||||
{
|
||||
return count % 2 == 0 ? back : front;
|
||||
}
|
||||
|
||||
BitGroupVector<> &read_buffer(int count)
|
||||
{
|
||||
return count % 2 == 0 ? front : back;
|
||||
}
|
||||
};
|
||||
|
||||
static void grow_shrink_visibility_grid(Depsgraph &depsgraph,
|
||||
Object &object,
|
||||
PBVH &pbvh,
|
||||
const Span<PBVHNode *> nodes,
|
||||
const VisAction action,
|
||||
const int iterations)
|
||||
{
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
SubdivCCG &subdiv_ccg = *object.sculpt->subdiv_ccg;
|
||||
|
||||
BitGroupVector<> &grid_hidden = BKE_subdiv_ccg_grid_hidden_ensure(subdiv_ccg);
|
||||
|
||||
const bool desired_state = action_to_hide(action);
|
||||
const CCGKey key = *BKE_pbvh_get_grid_key(pbvh);
|
||||
|
||||
DualBitBuffer buffers;
|
||||
Sean-Kim marked this conversation as resolved
Hans Goudey
commented
Can be written as:
Can be written as:
```cpp
DualBitBuffer buffers;
buffers.front = grid_hidden;
buffers.back = grid_hidden;
|
||||
buffers.front = BitGroupVector<>(grid_hidden);
|
||||
buffers.back = BitGroupVector<>(grid_hidden);
|
||||
|
||||
Array<bool> node_changed(nodes.size());
|
||||
Sean-Kim marked this conversation as resolved
Hans Goudey
commented
Should probably be initialized to false here Should probably be initialized to false here
|
||||
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
Sean-Kim marked this conversation as resolved
Hans Goudey
commented
`for (const int i : IndexRange(iterations)) {`
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int node_index : range) {
|
||||
PBVHNode *node = nodes[node_index];
|
||||
const Span<int> grids = bke::pbvh::node_grid_indices(*node);
|
||||
|
||||
for (const int grid_index : grids) {
|
||||
for (const int y : IndexRange(key.grid_size)) {
|
||||
for (const int x : IndexRange(key.grid_size)) {
|
||||
const int grid_elem_idx = elem_xy_to_index(x, y, key.grid_size);
|
||||
if (buffers.read_buffer(i)[grid_index][grid_elem_idx] != desired_state) {
|
||||
Sean-Kim marked this conversation as resolved
Hans Goudey
commented
Seems nice to extract the Seems nice to extract the `read_buffer` and `write_buffer` calls to before the parallel loop
|
||||
continue;
|
||||
}
|
||||
|
||||
SubdivCCGCoord coord{};
|
||||
coord.grid_index = grid_index;
|
||||
coord.x = x;
|
||||
coord.y = y;
|
||||
|
||||
SubdivCCGNeighbors neighbors;
|
||||
BKE_subdiv_ccg_neighbor_coords_get(subdiv_ccg, coord, true, neighbors);
|
||||
|
||||
for (const int j : neighbors.coords.index_range()) {
|
||||
const SubdivCCGCoord neighbor = neighbors.coords[j];
|
||||
const int neighbor_grid_elem_idx = elem_xy_to_index(
|
||||
neighbor.x, neighbor.y, key.grid_size);
|
||||
|
||||
buffers.write_buffer(i)[neighbor.grid_index][neighbor_grid_elem_idx].set(
|
||||
desired_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node_changed[node_index] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int node_index : range) {
|
||||
if (!node_changed[node_index]) {
|
||||
Sean-Kim marked this conversation as resolved
Hans Goudey
commented
In general skipping work inside of a parallel loop can be problematic. The tasks are scheduled assuming each index has the same amount of work. For example, if all the changed nodes happen to be grouped around specific indices, that means one thread can be stuck with a group of changed nodes while other threads happen to take the early out here. The alternative would be creating an index mask based on In general skipping work inside of a parallel loop can be problematic. The tasks are scheduled assuming each index has the same amount of work. For example, if all the changed nodes happen to be grouped around specific indices, that means one thread can be stuck with a group of changed nodes while other threads happen to take the early out here.
The alternative would be creating an index mask based on `node_changed` using `IndexMask::from_bools` and using `foreach_index(GrainSize(1)` for iteration
Sean Kim
commented
Oh, good to know. I wasn't aware that that's how TBB operated Oh, good to know. I wasn't aware that that's how TBB operated
|
||||
continue;
|
||||
}
|
||||
undo::push_node(object, nodes[node_index], undo::Type::HideVert);
|
||||
}
|
||||
});
|
||||
|
||||
BitGroupVector<> &last_buffer = buffers.write_buffer(iterations - 1);
|
||||
for (int i = 0; i < grid_hidden.size(); i++) {
|
||||
Sean-Kim marked this conversation as resolved
Hans Goudey
commented
The same as The same as `grid_hidden = std::move(last_buffer);`, no?
Sean Kim
commented
Yep, makes sense. I'm still not super solid on move semantics but it's good to keep in mind. Yep, makes sense. I'm still not super solid on move semantics but it's good to keep in mind.
|
||||
for (int j = 0; j < grid_hidden.group_size(); j++) {
|
||||
grid_hidden[i][j].set_branchless(last_buffer[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int node_index : range) {
|
||||
if (!node_changed[node_index]) {
|
||||
continue;
|
||||
}
|
||||
PBVHNode *node = nodes[node_index];
|
||||
|
||||
BKE_pbvh_node_mark_update_visibility(node);
|
||||
bke::pbvh::node_update_visibility_grids(grid_hidden, *node);
|
||||
}
|
||||
});
|
||||
|
||||
multires_mark_as_modified(&depsgraph, &object, MULTIRES_HIDDEN_MODIFIED);
|
||||
BKE_pbvh_sync_visibility_from_verts(pbvh, &mesh);
|
||||
}
|
||||
|
||||
static Array<bool> duplicate_visibility(const Object &object)
|
||||
Sean-Kim marked this conversation as resolved
Hans Goudey
commented
`duplicate_visibility_bmesh` maybe?
|
||||
{
|
||||
const SculptSession &ss = *object.sculpt;
|
||||
BMesh &bm = *ss.bm;
|
||||
Array<bool> result(bm.totvert);
|
||||
BM_mesh_elem_table_ensure(&bm, BM_VERT);
|
||||
for (const int i : result.index_range()) {
|
||||
result[i] = BM_elem_flag_test_bool(BM_vert_at_index(&bm, i), BM_ELEM_HIDDEN);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void grow_shrink_visibility_bmesh(Object &object,
|
||||
PBVH &pbvh,
|
||||
const Span<PBVHNode *> nodes,
|
||||
const VisAction action,
|
||||
const int iterations)
|
||||
{
|
||||
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
const Array<bool> prev_visibility = duplicate_visibility(object);
|
||||
partialvis_update_bmesh_nodes(object, nodes, action, [&](const BMVert *vert) {
|
||||
int vi = BM_elem_index_get(vert);
|
||||
PBVHVertRef vref = BKE_pbvh_index_to_vertex(pbvh, vi);
|
||||
SculptVertexNeighborIter ni;
|
||||
|
||||
bool should_change = false;
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vref, ni) {
|
||||
if (prev_visibility[ni.index] == action_to_hide(action)) {
|
||||
/* Not returning instantly to avoid leaking memory. */
|
||||
should_change = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
||||
return should_change;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static int visibility_filter_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Object &object = *CTX_data_active_object(C);
|
||||
Depsgraph &depsgraph = *CTX_data_ensure_evaluated_depsgraph(C);
|
||||
|
||||
PBVH &pbvh = *BKE_sculpt_object_pbvh_ensure(&depsgraph, &object);
|
||||
BLI_assert(BKE_object_sculpt_pbvh_get(&object) == &pbvh);
|
||||
|
||||
const VisAction mode = VisAction(RNA_enum_get(op->ptr, "action"));
|
||||
|
||||
Vector<PBVHNode *> nodes = bke::pbvh::search_gather(pbvh, {});
|
||||
|
||||
const SculptSession &ss = *object.sculpt;
|
||||
int num_verts = SCULPT_vertex_count_get(ss);
|
||||
|
||||
int iterations = RNA_int_get(op->ptr, "iterations");
|
||||
|
||||
if (RNA_boolean_get(op->ptr, "auto_iteration_count")) {
|
||||
/* Automatically adjust the number of iterations based on the number
|
||||
* of vertices in the mesh. */
|
||||
iterations = int(num_verts / VERTEX_ITERATION_THRESHOLD) + 1;
|
||||
}
|
||||
|
||||
undo::push_begin(object, op);
|
||||
switch (BKE_pbvh_type(pbvh)) {
|
||||
case PBVH_FACES:
|
||||
grow_shrink_visibility_mesh(object, nodes, mode, iterations);
|
||||
break;
|
||||
case PBVH_GRIDS:
|
||||
grow_shrink_visibility_grid(depsgraph, object, pbvh, nodes, mode, iterations);
|
||||
break;
|
||||
case PBVH_BMESH:
|
||||
grow_shrink_visibility_bmesh(object, pbvh, nodes, mode, iterations);
|
||||
break;
|
||||
}
|
||||
undo::push_end(object);
|
||||
|
||||
SCULPT_topology_islands_invalidate(*object.sculpt);
|
||||
tag_update_visibility(*C);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void PAINT_OT_visibility_filter(wmOperatorType *ot)
|
||||
{
|
||||
static EnumPropertyItem actions[] = {
|
||||
{int(VisAction::Show),
|
||||
"GROW",
|
||||
0,
|
||||
"Grow Visibility",
|
||||
"Grows the visibility by one face based on mesh topology"},
|
||||
Sean-Kim marked this conversation as resolved
Hans Goudey
commented
It's usually preferred to use the imperative tense for descriptions. For example: It's usually preferred to use the imperative tense for descriptions. For example: `Grow the visibility`, `Shrink the visibility`, `Edit the visibility`
|
||||
{int(VisAction::Hide),
|
||||
"SHRINK",
|
||||
0,
|
||||
"Shrink Visibility",
|
||||
"Shrinks the visibility by one face based on mesh topology"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
ot->name = "Visibility Filter";
|
||||
ot->idname = "PAINT_OT_visibility_filter";
|
||||
ot->description = "Edits the visibility of the current mesh";
|
||||
|
||||
ot->exec = visibility_filter_exec;
|
||||
ot->poll = SCULPT_mode_poll_view3d;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
RNA_def_enum(ot->srna, "action", actions, int(VisAction::Show), "Action", "");
|
||||
|
||||
RNA_def_int(ot->srna,
|
||||
"iterations",
|
||||
1,
|
||||
1,
|
||||
100,
|
||||
"Iterations",
|
||||
"Number of times that the filter is going to be applied",
|
||||
1,
|
||||
100);
|
||||
RNA_def_boolean(
|
||||
ot->srna,
|
||||
"auto_iteration_count",
|
||||
true,
|
||||
"Auto Iteration Count",
|
||||
"Use an automatic number of iterations based on the number of vertices of the sculpt");
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
|
@ -461,6 +461,7 @@ void PAINT_OT_hide_show_line_gesture(wmOperatorType *ot);
|
|||
void PAINT_OT_hide_show_polyline_gesture(wmOperatorType *ot);
|
||||
|
||||
void PAINT_OT_visibility_invert(wmOperatorType *ot);
|
||||
void PAINT_OT_visibility_filter(wmOperatorType *ot);
|
||||
} // namespace blender::ed::sculpt_paint::hide
|
||||
|
||||
/* `paint_mask.cc` */
|
||||
|
|
|
@ -1561,6 +1561,7 @@ void ED_operatortypes_paint()
|
|||
WM_operatortype_append(hide::PAINT_OT_hide_show_line_gesture);
|
||||
WM_operatortype_append(hide::PAINT_OT_hide_show_polyline_gesture);
|
||||
WM_operatortype_append(hide::PAINT_OT_visibility_invert);
|
||||
WM_operatortype_append(hide::PAINT_OT_visibility_filter);
|
||||
|
||||
/* paint masking */
|
||||
WM_operatortype_append(mask::PAINT_OT_mask_flood_fill);
|
||||
|
|
for (const int i : neighbors.coords.index_range().drop_front(neighbors.num_duplicates))
?