From 3fc29d80802392732e00de0a1c055de48239cd48 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Thu, 18 Apr 2024 16:48:19 +0200 Subject: [PATCH] Fix #120239: Snap in edit mode is ignoring some self elements Fix snapping issues caused by commit 1c77779160, where a mesh representing the edited mesh was introduced. Mesh snap to vertex now works in the following order: - Snap to vertices of visible triangles - Snap to vertices of loose edges - Snap to loose vertices The problem arises because in editing mode, faces whose vertices are being transformed are ignored, marked as hidden in the snap, resulting in the loss of some vertices in triangles. The solution involves considering the edges and vertices of hidden faces as loose elements since, despite being connected to faces, they are still visible to snap. Two new types of BVHTree were created for this purpose: - BVHTREE_FROM_LOOSEVERTS_NO_HIDDEN - BVHTREE_FROM_LOOSEEDGES_NO_HIDDEN This modification addresses two related issues: - snapping in edit mode to vertices and edges of a face being transformed - snapping to mesh with hidden loose elements Optionally, the first issue could be tackled separately by generating BVHTrees within the snap system itself and storing them in a `SnapCache_EditMesh *em_cache`, then passing this cache as a parameter to the `snap_object_mesh` function. Pull Request: https://projects.blender.org/blender/blender/pulls/120270 --- source/blender/blenkernel/BKE_bvhutils.hh | 6 ++ source/blender/blenkernel/intern/bvhutils.cc | 100 +++++++++++++++++- .../transform/transform_snap_object.hh | 2 +- .../transform/transform_snap_object_mesh.cc | 41 ++++--- 4 files changed, 133 insertions(+), 16 deletions(-) diff --git a/source/blender/blenkernel/BKE_bvhutils.hh b/source/blender/blenkernel/BKE_bvhutils.hh index 80de92dc24e..076e28faefe 100644 --- a/source/blender/blenkernel/BKE_bvhutils.hh +++ b/source/blender/blenkernel/BKE_bvhutils.hh @@ -55,6 +55,12 @@ enum BVHCacheType { BVHTREE_FROM_LOOSEVERTS, BVHTREE_FROM_LOOSEEDGES, + /* These types consider geometry visibility when getting loose elements. + * NOTE: If the element is linked to a face or edge that is hidden, but the element itself is not + * hidden, it is considered a loose element. */ + BVHTREE_FROM_LOOSEVERTS_NO_HIDDEN, + BVHTREE_FROM_LOOSEEDGES_NO_HIDDEN, + /* Keep `BVHTREE_MAX_ITEM` as last item. */ BVHTREE_MAX_ITEM, }; diff --git a/source/blender/blenkernel/intern/bvhutils.cc b/source/blender/blenkernel/intern/bvhutils.cc index be24b3c4771..08af4a4509d 100644 --- a/source/blender/blenkernel/intern/bvhutils.cc +++ b/source/blender/blenkernel/intern/bvhutils.cc @@ -509,14 +509,15 @@ static void bvhtree_from_mesh_setup_data(BVHTree *tree, switch (bvh_cache_type) { case BVHTREE_FROM_VERTS: case BVHTREE_FROM_LOOSEVERTS: + case BVHTREE_FROM_LOOSEVERTS_NO_HIDDEN: /* a nullptr nearest callback works fine * remember the min distance to point is the same as the min distance to BV of point */ r_data->nearest_callback = nullptr; r_data->raycast_callback = mesh_verts_spherecast; break; - case BVHTREE_FROM_EDGES: case BVHTREE_FROM_LOOSEEDGES: + case BVHTREE_FROM_LOOSEEDGES_NO_HIDDEN: r_data->nearest_callback = mesh_edges_nearest_point; r_data->raycast_callback = mesh_edges_spherecast; break; @@ -780,6 +781,89 @@ BVHTree *bvhtree_from_mesh_corner_tris_ex(BVHTreeFromMesh *data, return tree; } +static BitVector<> loose_verts_no_hidden_mask_get(const Mesh &mesh, int *r_elem_active_len) +{ + using namespace blender; + using namespace blender::bke; + + int count = mesh.verts_num; + BitVector<> verts_mask(count, true); + + const AttributeAccessor attributes = mesh.attributes(); + const Span edges = mesh.edges(); + const VArray hide_edge = *attributes.lookup_or_default( + ".hide_edge", AttrDomain::Edge, false); + const VArray hide_vert = *attributes.lookup_or_default( + ".hide_vert", AttrDomain::Point, false); + + for (const int i : edges.index_range()) { + if (hide_edge[i]) { + continue; + } + for (const int vert : {edges[i][0], edges[i][1]}) { + if (verts_mask[vert]) { + verts_mask[vert].reset(); + count--; + } + } + } + + if (count) { + for (const int vert : verts_mask.index_range()) { + if (verts_mask[vert] && hide_vert[vert]) { + verts_mask[vert].reset(); + count--; + } + } + } + + *r_elem_active_len = count; + + return verts_mask; +} + +static BitVector<> loose_edges_no_hidden_mask_get(const Mesh &mesh, int *r_elem_active_len) +{ + using namespace blender; + using namespace blender::bke; + + int count = mesh.edges_num; + BitVector<> edge_mask(count, true); + + const AttributeAccessor attributes = mesh.attributes(); + const OffsetIndices faces = mesh.faces(); + const Span corner_edges = mesh.corner_edges(); + const VArray hide_poly = *attributes.lookup_or_default( + ".hide_poly", AttrDomain::Face, false); + const VArray hide_edge = *attributes.lookup_or_default( + ".hide_edge", AttrDomain::Edge, false); + + for (const int i : faces.index_range()) { + if (hide_poly[i]) { + continue; + } + for (const int edge : corner_edges.slice(faces[i])) { + if (edge_mask[edge]) { + edge_mask[edge].reset(); + count--; + } + } + } + + if (count) { + for (const int edge : edge_mask.index_range()) { + if (edge_mask[edge] && hide_edge[edge]) { + edge_mask[edge].reset(); + count--; + } + } + } + + *r_elem_active_len = count; + + return edge_mask; +} + static BitVector<> corner_tris_no_hidden_map_get(const blender::OffsetIndices faces, const VArray &hide_poly, const int corner_tris_len, @@ -860,6 +944,13 @@ BVHTree *BKE_bvhtree_from_mesh_get(BVHTreeFromMesh *data, 0.0f, tree_type, 6, positions, loose_verts.is_loose_bits, loose_verts.count); break; } + case BVHTREE_FROM_LOOSEVERTS_NO_HIDDEN: { + int mask_bits_act_len = -1; + const BitVector<> mask = loose_verts_no_hidden_mask_get(*mesh, &mask_bits_act_len); + data->tree = bvhtree_from_mesh_verts_create_tree( + 0.0f, tree_type, 6, positions, mask, mask_bits_act_len); + break; + } case BVHTREE_FROM_VERTS: { data->tree = bvhtree_from_mesh_verts_create_tree(0.0f, tree_type, 6, positions, {}, -1); break; @@ -870,6 +961,13 @@ BVHTree *BKE_bvhtree_from_mesh_get(BVHTreeFromMesh *data, positions, edges, loose_edges.is_loose_bits, loose_edges.count, 0.0f, tree_type, 6); break; } + case BVHTREE_FROM_LOOSEEDGES_NO_HIDDEN: { + int mask_bits_act_len = -1; + const BitVector<> mask = loose_edges_no_hidden_mask_get(*mesh, &mask_bits_act_len); + data->tree = bvhtree_from_mesh_edges_create_tree( + positions, edges, mask, mask_bits_act_len, 0.0f, tree_type, 6); + break; + } case BVHTREE_FROM_EDGES: { data->tree = bvhtree_from_mesh_edges_create_tree( positions, edges, {}, -1, 0.0f, tree_type, 6); diff --git a/source/blender/editors/transform/transform_snap_object.hh b/source/blender/editors/transform/transform_snap_object.hh index 34fccff1559..deecb55effa 100644 --- a/source/blender/editors/transform/transform_snap_object.hh +++ b/source/blender/editors/transform/transform_snap_object.hh @@ -227,7 +227,7 @@ eSnapMode snap_object_mesh(SnapObjectContext *sctx, const ID *id, const blender::float4x4 &obmat, eSnapMode snap_to_flag, - bool use_hide); + bool skip_hidden); eSnapMode snap_polygon_mesh(SnapObjectContext *sctx, const Object *ob_eval, diff --git a/source/blender/editors/transform/transform_snap_object_mesh.cc b/source/blender/editors/transform/transform_snap_object_mesh.cc index 6049f54c807..a8df25c83d5 100644 --- a/source/blender/editors/transform/transform_snap_object_mesh.cc +++ b/source/blender/editors/transform/transform_snap_object_mesh.cc @@ -29,14 +29,14 @@ using namespace blender; * \{ */ static void snap_object_data_mesh_get(const Mesh *mesh_eval, - bool use_hide, + bool skip_hidden, BVHTreeFromMesh *r_treedata) { /* The BVHTree from corner_tris is always required. */ BKE_bvhtree_from_mesh_get(r_treedata, mesh_eval, - use_hide ? BVHTREE_FROM_CORNER_TRIS_NO_HIDDEN : - BVHTREE_FROM_CORNER_TRIS, + skip_hidden ? BVHTREE_FROM_CORNER_TRIS_NO_HIDDEN : + BVHTREE_FROM_CORNER_TRIS, 4); } @@ -424,9 +424,13 @@ eSnapMode snap_edge_points_mesh(SnapObjectContext *sctx, return elem; } -static eSnapMode mesh_snap_mode_supported(const Mesh *mesh) +static eSnapMode mesh_snap_mode_supported(const Mesh *mesh, bool skip_hidden) { - eSnapMode snap_mode_supported = mesh->loose_verts().count ? SCE_SNAP_TO_POINT : SCE_SNAP_TO_NONE; + /* When skipping hidden geometry, we still cannot obtain the number of loose verts + * until computing #BVHTREE_FROM_LOOSEVERTS_NO_HIDDEN. Therefore, consider #SCE_SNAP_TO_POINT + * supported even if the mesh has no loose vertices in this case. */ + eSnapMode snap_mode_supported = (skip_hidden || mesh->loose_verts().count) ? SCE_SNAP_TO_POINT : + SCE_SNAP_TO_NONE; if (mesh->faces_num) { snap_mode_supported |= SCE_SNAP_TO_FACE | SCE_SNAP_INDIVIDUAL_NEAREST | SNAP_TO_EDGE_ELEMENTS; } @@ -441,7 +445,7 @@ static eSnapMode snapMesh(SnapObjectContext *sctx, const Object *ob_eval, const Mesh *mesh_eval, const float4x4 &obmat, - bool use_hide, + bool skip_hidden, eSnapMode snap_to) { BLI_assert(snap_to != SCE_SNAP_TO_FACE); @@ -455,19 +459,28 @@ static eSnapMode snapMesh(SnapObjectContext *sctx, } } - snap_to &= mesh_snap_mode_supported(mesh_eval) & (SNAP_TO_EDGE_ELEMENTS | SCE_SNAP_TO_POINT); + snap_to &= mesh_snap_mode_supported(mesh_eval, skip_hidden) & + (SNAP_TO_EDGE_ELEMENTS | SCE_SNAP_TO_POINT); if (snap_to == SCE_SNAP_TO_NONE) { return SCE_SNAP_TO_NONE; } BVHTreeFromMesh treedata, treedata_dummy; - snap_object_data_mesh_get(mesh_eval, use_hide, &treedata); + snap_object_data_mesh_get(mesh_eval, skip_hidden, &treedata); BVHTree *bvhtree[2] = {nullptr}; - bvhtree[0] = BKE_bvhtree_from_mesh_get(&treedata_dummy, mesh_eval, BVHTREE_FROM_LOOSEEDGES, 2); + bvhtree[0] = BKE_bvhtree_from_mesh_get(&treedata_dummy, + mesh_eval, + skip_hidden ? BVHTREE_FROM_LOOSEEDGES_NO_HIDDEN : + BVHTREE_FROM_LOOSEEDGES, + 2); BLI_assert(treedata_dummy.cached); if (snap_to & SCE_SNAP_TO_POINT) { - bvhtree[1] = BKE_bvhtree_from_mesh_get(&treedata_dummy, mesh_eval, BVHTREE_FROM_LOOSEVERTS, 2); + bvhtree[1] = BKE_bvhtree_from_mesh_get(&treedata_dummy, + mesh_eval, + skip_hidden ? BVHTREE_FROM_LOOSEVERTS_NO_HIDDEN : + BVHTREE_FROM_LOOSEVERTS, + 2); BLI_assert(treedata_dummy.cached); } @@ -583,26 +596,26 @@ eSnapMode snap_object_mesh(SnapObjectContext *sctx, const ID *id, const float4x4 &obmat, eSnapMode snap_to_flag, - bool use_hide) + bool skip_hidden) { eSnapMode elem = SCE_SNAP_TO_NONE; const Mesh *mesh_eval = reinterpret_cast(id); if (snap_to_flag & (SNAP_TO_EDGE_ELEMENTS | SCE_SNAP_TO_POINT)) { - elem = snapMesh(sctx, ob_eval, mesh_eval, obmat, use_hide, snap_to_flag); + elem = snapMesh(sctx, ob_eval, mesh_eval, obmat, skip_hidden, snap_to_flag); if (elem) { return elem; } } if (snap_to_flag & SCE_SNAP_TO_FACE) { - if (raycastMesh(sctx, ob_eval, mesh_eval, obmat, sctx->runtime.object_index++, use_hide)) { + if (raycastMesh(sctx, ob_eval, mesh_eval, obmat, sctx->runtime.object_index++, skip_hidden)) { return SCE_SNAP_TO_FACE; } } if (snap_to_flag & SCE_SNAP_INDIVIDUAL_NEAREST) { - if (nearest_world_mesh(sctx, ob_eval, mesh_eval, obmat, use_hide)) { + if (nearest_world_mesh(sctx, ob_eval, mesh_eval, obmat, skip_hidden)) { return SCE_SNAP_INDIVIDUAL_NEAREST; } }