Refactor: Weight Paint Select Linked Faces #104577

Merged
Christoph Lendenfeld merged 15 commits from ChrisLend/blender:weight_paint_refactor_face_select into main 2023-02-23 08:26:44 +01:00
1 changed files with 105 additions and 69 deletions

View File

@ -212,106 +212,142 @@ void paintface_reveal(bContext *C, Object *ob, const bool select)
paintface_flush_flags(C, ob, true, true);
}
/* Set object-mode face selection seams based on edge data, uses hash table to find seam edges. */
static void select_linked_tfaces_with_seams(Mesh *me, const uint index, const bool select)
/**
* Join all edges of each poly in the AtomicDisjointSet. This can be used to find out which polys
ChrisLend marked this conversation as resolved
Review

Mesh *mesh -> Mesh &mesh

Using a reference indicates that the pointer won't be null

`Mesh *mesh` -> `Mesh &mesh` Using a reference indicates that the pointer won't be null
* are connected to each other.
* \param islands Is expected to be of length mesh->totedge.
ChrisLend marked this conversation as resolved
Review
`@param islands` -> `\param islands:` https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Comments
* \param skip_seams Polys separated by a seam will be treated as not connected.
*/
static void build_poly_connections(blender::AtomicDisjointSet &islands,
Mesh &mesh,
const bool skip_seams = true)
Review

Is there a reason to have the skip_seams argument right now? If not, it can always be added easily later.

Is there a reason to have the `skip_seams` argument right now? If not, it can always be added easily later.

My idea was that it makes it explicit what the function is doing. I thought it's not immediately obvious that it would stop at seams.

Let me know what you think. I can remove it until needed otherwise

My idea was that it makes it explicit what the function is doing. I thought it's not immediately obvious that it would stop at seams. Let me know what you think. I can remove it until needed otherwise
Review

Not sure, I feel like there must be a better way to indicate the purpose besides adding an unused argument-- the name or the docstring? But it's also not a big deal at all.

Not sure, I feel like there must be a better way to indicate the purpose besides adding an unused argument-- the name or the docstring? But it's also not a big deal at all.
{
using namespace blender;
bool do_it = true;
bool mark = false;
const Span<MPoly> polys = mesh.polys();
const Span<MEdge> edges = mesh.edges();
const Span<MLoop> loops = mesh.loops();
BLI_bitmap *edge_tag = BLI_BITMAP_NEW(me->totedge, __func__);
BLI_bitmap *poly_tag = BLI_BITMAP_NEW(me->totpoly, __func__);
const Span<MEdge> edges = me->edges();
const Span<MPoly> polys = me->polys();
const Span<MLoop> loops = me->loops();
bke::MutableAttributeAccessor attributes = me->attributes_for_write();
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
".hide_poly", ATTR_DOMAIN_FACE, false);
ChrisLend marked this conversation as resolved
Review

Maybe I'm forgetting something, but why is it that we can ignore hide_poly now?

Maybe I'm forgetting something, but why is it that we can ignore `hide_poly` now?

thanks I missed that. Of course we can't ignore that :)

thanks I missed that. Of course we can't ignore that :)
/* Polys are connected if they share edges. By connecting all edges of a loop (as long as they
* are not a seam) we can find connected faces. */
threading::parallel_for(polys.index_range(), 1024, [&](const IndexRange range) {
ChrisLend marked this conversation as resolved
Review

Grammar: "Unless the are"

Also, the previous comment said the same thing, this is redundant.

Grammar: "Unless the are" Also, the previous comment said the same thing, this is redundant.
for (const int poly_index : range) {
ChrisLend marked this conversation as resolved
Review

for (const int loop : IndexRange(poly.loopstart, poly.totloop)) {

`for (const int loop : IndexRange(poly.loopstart, poly.totloop)) {`
if (hide_poly[poly_index]) {
continue;
}
const MPoly &poly = polys[poly_index];
ChrisLend marked this conversation as resolved
Review

I suggest using a separate const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop); variable. Then the next loop can use for (const int outer_loop_index : poly_loops.index_range()) { and you don't have to add poly.totloop every time a loop in the poly is accessed.

I suggest using a separate `const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);` variable. Then the next loop can use `for (const int outer_loop_index : poly_loops.index_range()) {` and you don't have to add `poly.totloop` every time a loop in the poly is accessed.
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
Review

Use references instead of pointers, that should be your default in C++ code.

Use references instead of pointers, that should be your default in C++ code.

sure thing, missed that
out of curiosity: what is the reason for it?

sure thing, missed that out of curiosity: what is the reason for it?
Review

There are quite a few benefits-- they indicate non-null, they allow using . instead of ->, they can't be indexed (so they can't be mistaken for pointers to arrays). There's more info here:

https://stackoverflow.com/questions/57483/what-are-the-differences-between-a-pointer-variable-and-a-reference-variable

There are quite a few benefits-- they indicate non-null, they allow using `.` instead of `->`, they can't be indexed (so they can't be mistaken for pointers to arrays). There's more info here: https://stackoverflow.com/questions/57483/what-are-the-differences-between-a-pointer-variable-and-a-reference-variable
for (const int poly_loop_index : poly_loops.index_range()) {
const MLoop &outer_mloop = poly_loops[poly_loop_index];
if (skip_seams && (edges[outer_mloop.e].flag & ME_SEAM) != 0) {
continue;
}
for (const MLoop &inner_mloop :
poly_loops.slice(poly_loop_index, poly_loops.size() - poly_loop_index)) {
if (&outer_mloop == &inner_mloop) {
continue;
}
if (skip_seams && (edges[inner_mloop.e].flag & ME_SEAM) != 0) {
continue;
}
islands.join(inner_mloop.e, outer_mloop.e);
}
}
}
});
}
/* Select faces connected to the given face_indices. Seams are treated as separation. */
static void paintface_select_linked_faces(Mesh &mesh,
const blender::Span<int> face_indices,
ChrisLend marked this conversation as resolved
Review

MPoly poly -> const MPoly &poly

`MPoly poly` -> `const MPoly &poly`
const bool select)
{
Review

Try to structure C++ code so that you don't resort to using raw pointers. That will usually lead to more readable code. The C-style for loop syntax is another good thing to avoid.

In this case:

for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {

This applies to other areas in this function too

Try to structure C++ code so that you don't resort to using raw pointers. That will usually lead to more readable code. The C-style for loop syntax is another good thing to avoid. In this case: `for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {` This applies to other areas in this function too

I changed out all for loops except for (int inner_loop_index = outer_loop_index + 1; inner_loop_index < poly.totloop; inner_loop_index++)

I found it a bit confusing to use IndexRange for that use case since I'd have to subtract from the length each time. Might just be me but I find the classic syntax reads clearer in that case

I changed out all for loops except `for (int inner_loop_index = outer_loop_index + 1; inner_loop_index < poly.totloop; inner_loop_index++)` I found it a bit confusing to use `IndexRange` for that use case since I'd have to subtract from the length each time. Might just be me but I find the classic syntax reads clearer in that case
Review

Yeah, since the constructor uses start, size rather than start, end, that makes sense.

Yeah, since the constructor uses `start, size` rather than `start, end`, that makes sense.

using const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop); as you suggested also solved that issue :)

using `const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);` as you suggested also solved that issue :)
using namespace blender;
AtomicDisjointSet islands(mesh.totedge);
build_poly_connections(islands, mesh);
const Span<MPoly> polys = mesh.polys();
const Span<MEdge> edges = mesh.edges();
const Span<MLoop> loops = mesh.loops();
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
bke::SpanAttributeWriter<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
".select_poly", ATTR_DOMAIN_FACE);
if (index != uint(-1)) {
/* only put face under cursor in array */
const MPoly &poly = polys[index];
BKE_mesh_poly_edgebitmap_insert(edge_tag, &poly, &loops[poly.loopstart]);
BLI_BITMAP_ENABLE(poly_tag, index);
}
else {
/* fill array by selection */
for (int i = 0; i < me->totpoly; i++) {
if (hide_poly[i]) {
/* pass */
}
else if (select_poly.span[i]) {
const MPoly &poly = polys[i];
BKE_mesh_poly_edgebitmap_insert(edge_tag, &poly, &loops[poly.loopstart]);
BLI_BITMAP_ENABLE(poly_tag, i);
}
}
}
while (do_it) {
do_it = false;
/* expand selection */
for (int i = 0; i < me->totpoly; i++) {
if (hide_poly[i]) {
Set<int> selected_roots;
for (const int i : face_indices) {
const MPoly &poly = polys[i];
ChrisLend marked this conversation as resolved
Review

Add using namespace blender here so it's not necessary below.

Add `using namespace blender` here so it's not necessary below.
for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {
if ((edges[loop.e].flag & ME_SEAM) != 0) {
continue;
}
const int root = islands.find_root(loop.e);
selected_roots.add(root);
}
}
if (!BLI_BITMAP_TEST(poly_tag, i)) {
mark = false;
const MPoly &poly = polys[i];
const MLoop *ml = &loops[poly.loopstart];
for (int b = 0; b < poly.totloop; b++, ml++) {
if ((edges[ml->e].flag & ME_SEAM) == 0) {
if (BLI_BITMAP_TEST(edge_tag, ml->e)) {
mark = true;
break;
}
}
}
if (mark) {
BLI_BITMAP_ENABLE(poly_tag, i);
BKE_mesh_poly_edgebitmap_insert(edge_tag, &poly, &loops[poly.loopstart]);
do_it = true;
threading::parallel_for(select_poly.span.index_range(), 1024, [&](const IndexRange range) {
for (const int poly_index : range) {
const MPoly &poly = polys[poly_index];
for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {
const int root = islands.find_root(loop.e);
ChrisLend marked this conversation as resolved
Review

Looks like this early return will skip select_poly.finish();. That will probably print a warning to the terminal.

Looks like this early return will skip `select_poly.finish();`. That will probably print a warning to the terminal.
if (selected_roots.contains(root)) {
select_poly.span[poly_index] = select;
break;
}
}
}
}
});
MEM_freeN(edge_tag);
for (int i = 0; i < me->totpoly; i++) {
if (BLI_BITMAP_TEST(poly_tag, i)) {
select_poly.span[i] = select;
}
}
MEM_freeN(poly_tag);
select_poly.finish();
}
void paintface_select_linked(bContext *C, Object *ob, const int mval[2], const bool select)
ChrisLend marked this conversation as resolved
Review

Declare this closer to where it's first used, right above if (mval) {

Declare this closer to where it's first used, right above `if (mval) {`
{
uint index = uint(-1);
using namespace blender;
Mesh *me = BKE_mesh_from_object(ob);
if (me == nullptr || me->totpoly == 0) {
return;
}
bke::MutableAttributeAccessor attributes = me->attributes_for_write();
bke::SpanAttributeWriter<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
".select_poly", ATTR_DOMAIN_FACE);
Vector<int> indices;
if (mval) {
uint index = uint(-1);
if (!ED_mesh_pick_face(C, ob, mval, ED_MESH_PICK_DEFAULT_FACE_DIST, &index)) {
select_poly.finish();
return;
}
/* Since paintface_select_linked_faces might not select the face under the cursor, select it
* here. */
select_poly.span[index] = true;
indices.append(index);
}
else {
for (const int i : select_poly.span.index_range()) {
if (!select_poly.span[i]) {
continue;
}
indices.append(i);
}
}
select_linked_tfaces_with_seams(me, index, select);
select_poly.finish();
paintface_select_linked_faces(*me, indices, select);
paintface_flush_flags(C, ob, true, false);
}