diff --git a/source/blender/blenkernel/intern/mesh_calc_edges.cc b/source/blender/blenkernel/intern/mesh_calc_edges.cc index 34f7609a324..cb98f33deb2 100644 --- a/source/blender/blenkernel/intern/mesh_calc_edges.cc +++ b/source/blender/blenkernel/intern/mesh_calc_edges.cc @@ -11,6 +11,7 @@ #include "DNA_object_types.h" #include "BLI_map.hh" +#include "BLI_ordered_edge.hh" #include "BLI_task.hh" #include "BLI_threads.h" #include "BLI_timeit.hh" @@ -21,43 +22,14 @@ namespace blender::bke::calc_edges { -/** This is used to uniquely identify edges in a hash map. */ -struct OrderedEdge { - int v_low, v_high; - - OrderedEdge(const int v1, const int v2) - { - if (v1 < v2) { - v_low = v1; - v_high = v2; - } - else { - v_low = v2; - v_high = v1; - } - } - - OrderedEdge(const uint v1, const uint v2) : OrderedEdge(int(v1), int(v2)) {} - - uint64_t hash() const - { - return (this->v_low << 8) ^ this->v_high; - } - - /** Return a hash value that is likely to be different in the low bits from the normal `hash()` - * function. This is necessary to avoid collisions in #BKE_mesh_calc_edges. */ - uint64_t hash2() const - { - return this->v_low; - } - - friend bool operator==(const OrderedEdge &e1, const OrderedEdge &e2) - { - BLI_assert(e1.v_low < e1.v_high); - BLI_assert(e2.v_low < e2.v_high); - return e1.v_low == e2.v_low && e1.v_high == e2.v_high; - } -}; +/** + * Return a hash value that is likely to be different in the low bits from the normal `hash()` + * function. This is necessary to avoid collisions in #BKE_mesh_calc_edges. + */ +static uint64_t edge_hash_2(const OrderedEdge &edge) +{ + return edge.v_low; +} /* The map first contains an edge pointer and later an index. */ union OrigEdgeOrIndex { @@ -86,7 +58,7 @@ static void add_existing_edges_to_hash_maps(Mesh *mesh, for (const int2 &edge : edges) { OrderedEdge ordered_edge{edge[0], edge[1]}; /* Only add the edge when it belongs into this map. */ - if (task_index == (parallel_mask & ordered_edge.hash2())) { + if (task_index == (parallel_mask & edge_hash_2(ordered_edge))) { edge_map.add_new(ordered_edge, {&edge}); } } @@ -109,7 +81,7 @@ static void add_polygon_edges_to_hash_maps(Mesh *mesh, if (vert_prev != vert) { OrderedEdge ordered_edge{vert_prev, vert}; /* Only add the edge when it belongs into this map. */ - if (task_index == (parallel_mask & ordered_edge.hash2())) { + if (task_index == (parallel_mask & edge_hash_2(ordered_edge))) { edge_map.lookup_or_add(ordered_edge, {nullptr}); } } @@ -171,7 +143,7 @@ static void update_edge_indices_in_poly_loops(const OffsetIndices polys, if (vert_prev != vert) { OrderedEdge ordered_edge{vert_prev, vert}; /* Double lookup: First find the map that contains the edge, then lookup the edge. */ - const EdgeMap &edge_map = edge_maps[parallel_mask & ordered_edge.hash2()]; + const EdgeMap &edge_map = edge_maps[parallel_mask & edge_hash_2(ordered_edge)]; edge_index = edge_map.lookup(ordered_edge).index; } else { diff --git a/source/blender/blenlib/BLI_ordered_edge.hh b/source/blender/blenlib/BLI_ordered_edge.hh new file mode 100644 index 00000000000..d07a6f6fc68 --- /dev/null +++ b/source/blender/blenlib/BLI_ordered_edge.hh @@ -0,0 +1,47 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_assert.h" +#include "BLI_math_vector_types.hh" + +namespace blender { + +/** + * A version of `int2` used as a key for hash-maps, agnostic of the arbitrary order of the two + * vertices in a mesh edge. + */ +struct OrderedEdge { + int v_low; + int v_high; + + OrderedEdge(const int v1, const int v2) + { + if (v1 < v2) { + v_low = v1; + v_high = v2; + } + else { + v_low = v2; + v_high = v1; + } + } + OrderedEdge(const int2 edge) : OrderedEdge(edge[0], edge[1]) {} + OrderedEdge(const uint v1, const uint v2) : OrderedEdge(int(v1), int(v2)) {} + + uint64_t hash() const + { + return (this->v_low << 8) ^ this->v_high; + } + + friend bool operator==(const OrderedEdge &e1, const OrderedEdge &e2) + { + BLI_assert(e1.v_low < e1.v_high); + BLI_assert(e2.v_low < e2.v_high); + return e1.v_low == e2.v_low && e1.v_high == e2.v_high; + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 21e24bb184e..c7afefa97b1 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -320,6 +320,7 @@ set(SRC BLI_noise.hh BLI_offset_indices.hh BLI_offset_span.hh + BLI_ordered_edge.hh BLI_parameter_pack_utils.hh BLI_path_util.h BLI_polyfill_2d.h diff --git a/source/blender/geometry/intern/mesh_split_edges.cc b/source/blender/geometry/intern/mesh_split_edges.cc index c16feea4b83..e99c38d5ea9 100644 --- a/source/blender/geometry/intern/mesh_split_edges.cc +++ b/source/blender/geometry/intern/mesh_split_edges.cc @@ -4,6 +4,8 @@ #include "BLI_array_utils.hh" #include "BLI_index_mask.hh" +#include "BLI_ordered_edge.hh" +#include "BLI_vector_set.hh" #include "BKE_attribute.hh" #include "BKE_attribute_math.hh" @@ -14,12 +16,6 @@ namespace blender::geometry { -/* Naively checks if the first vertices and the second vertices are the same. */ -static inline bool naive_edges_equal(const int2 &edge1, const int2 &edge2) -{ - return edge1 == edge2; -} - static void add_new_vertices(Mesh &mesh, const Span new_to_old_verts_map) { /* These types aren't supported for interpolation below. */ @@ -154,70 +150,6 @@ static void add_new_edges(Mesh &mesh, } } -/** - * Merge the new_edge into the original edge. - * - * NOTE: This function is very specific to the situation and makes a lot of assumptions. - */ -static void merge_edges(const int orig_edge_i, - const int new_edge_i, - MutableSpan new_corner_edges, - Vector> &edge_to_loop_map, - Vector &new_edges, - Vector &new_to_old_edges_map) -{ - /* Merge back into the original edge by undoing the topology changes. */ - BLI_assert(edge_to_loop_map[new_edge_i].size() == 1); - const int loop_i = edge_to_loop_map[new_edge_i][0]; - new_corner_edges[loop_i] = orig_edge_i; - - /* We are putting the last edge in the location of new_edge in all the maps, to remove - * new_edge efficiently. We have to update the topology information for this last edge - * though. Essentially we are replacing every instance of last_edge_i with new_edge_i. */ - const int last_edge_i = new_edges.size() - 1; - if (last_edge_i != new_edge_i) { - BLI_assert(edge_to_loop_map[last_edge_i].size() == 1); - const int last_edge_loop_i = edge_to_loop_map[last_edge_i][0]; - new_corner_edges[last_edge_loop_i] = new_edge_i; - } - - /* We can now safely swap-remove. */ - new_edges.remove_and_reorder(new_edge_i); - edge_to_loop_map.remove_and_reorder(new_edge_i); - new_to_old_edges_map.remove_and_reorder(new_edge_i); -} - -/** - * Replace the vertex of an edge with a new one, and update the connected loops. - * - * NOTE: This only updates the loops containing the edge and the old vertex. It should therefore - * also be called on the adjacent edge. - */ -static void swap_vertex_of_edge(int2 &edge, - const int old_vert, - const int new_vert, - MutableSpan corner_verts, - const Span connected_loops) -{ - if (edge[0] == old_vert) { - edge[0] = new_vert; - } - else if (edge[1] == old_vert) { - edge[1] = new_vert; - } - else { - BLI_assert_unreachable(); - } - - for (const int loop_i : connected_loops) { - if (corner_verts[loop_i] == old_vert) { - corner_verts[loop_i] = new_vert; - } - /* The old vertex is on the loop containing the adjacent edge. Since this function is also - * called on the adjacent edge, we don't replace it here. */ - } -} - /** Split the vertex into duplicates so that each fan has a different vertex. */ static void split_vertex_per_fan(const int vertex, const int start_offset, @@ -225,7 +157,6 @@ static void split_vertex_per_fan(const int vertex, const Span fans, const Span fan_sizes, const Span> edge_to_loop_map, - MutableSpan new_edges, MutableSpan corner_verts, MutableSpan new_to_old_verts_map) { @@ -237,8 +168,13 @@ static void split_vertex_per_fan(const int vertex, new_to_old_verts_map[new_vert_i - orig_verts_num] = vertex; for (const int edge_i : fans.slice(fan_start, fan_sizes[i])) { - swap_vertex_of_edge( - new_edges[edge_i], vertex, new_vert_i, corner_verts, edge_to_loop_map[edge_i]); + for (const int loop_i : edge_to_loop_map[edge_i]) { + if (corner_verts[loop_i] == vertex) { + corner_verts[loop_i] = new_vert_i; + } + /* The old vertex is on the loop containing the adjacent edge. Since this function is also + * called on the adjacent edge, we don't replace it here. */ + } } fan_start += fan_sizes[i]; } @@ -267,7 +203,7 @@ static int adjacent_edge(const Span corner_verts, * be used to retrieve the fans from connected_edges. */ static void calc_vertex_fans(const int vertex, - const Span new_corner_verts, + const Span corner_verts, const Span new_corner_edges, const OffsetIndices polys, const Span> edge_to_loop_map, @@ -298,7 +234,7 @@ static void calc_vertex_fans(const int vertex, /* Add adjacent edges to search stack. */ for (const int loop_i : edge_to_loop_map[curr_edge_i]) { const int adjacent_edge_i = adjacent_edge( - new_corner_verts, new_corner_edges, loop_i, polys[loop_to_poly_map[loop_i]], vertex); + corner_verts, new_corner_edges, loop_i, polys[loop_to_poly_map[loop_i]], vertex); /* Find out if this edge was visited already. */ int i = curr_i + 1; @@ -340,18 +276,13 @@ static void calc_vertex_fans(const int vertex, static void split_edge_per_poly(const int edge_i, const int new_edge_start, MutableSpan> edge_to_loop_map, - MutableSpan corner_edges, - MutableSpan new_edges, - MutableSpan new_to_old_edges_map) + MutableSpan corner_edges) { if (edge_to_loop_map[edge_i].size() <= 1) { return; } int new_edge_index = new_edge_start; for (const int loop_i : edge_to_loop_map[edge_i].as_span().drop_front(1)) { - const int2 &new_edge(new_edges[edge_i]); - new_edges[new_edge_index] = new_edge; - new_to_old_edges_map[new_edge_index] = edge_i; edge_to_loop_map[new_edge_index].append({loop_i}); corner_edges[loop_i] = new_edge_index; new_edge_index++; @@ -401,11 +332,11 @@ void split_edges(Mesh &mesh, }); const OffsetIndices polys = mesh.polys(); + const Array orig_corner_edges = mesh.corner_edges(); + IndexMaskMemory memory; + const IndexMask loose_edges = IndexMask::from_bits(mesh.loose_edges().is_loose_bits, memory); - MutableSpan corner_verts = mesh.corner_verts_for_write(); MutableSpan corner_edges = mesh.corner_edges_for_write(); - Vector new_edges(new_edges_size); - new_edges.as_mutable_span().take_front(edges.size()).copy_from(edges); Vector> edge_to_loop_map(new_edges_size); threading::parallel_for(edges.index_range(), 512, [&](const IndexRange range) { @@ -414,19 +345,18 @@ void split_edges(Mesh &mesh, } }); - /* Used for transferring attributes. */ - Vector new_to_old_edges_map(new_edges.size()); - std::iota(new_to_old_edges_map.begin(), new_to_old_edges_map.end(), 0); + /* Step 1: Split the edges. */ + threading::parallel_for(mask.index_range(), 512, [&](IndexRange range) { + for (const int mask_i : range) { + const int edge_i = mask[mask_i]; + split_edge_per_poly(edge_i, edge_offsets[edge_i], edge_to_loop_map, corner_edges); + } + }); /* Step 1: Split the edges. */ - mask.foreach_index(GrainSize(512), [&](const int edge_i) { - split_edge_per_poly(edge_i, - edge_offsets[edge_i], - edge_to_loop_map, - corner_edges, - new_edges, - new_to_old_edges_map); + mask.foreach_index([&](const int edge_i) { + split_edge_per_poly(edge_i, edge_offsets[edge_i], edge_to_loop_map, corner_edges); }); /* Step 1.5: Update topology information (can't parallelize). */ @@ -438,6 +368,8 @@ void split_edges(Mesh &mesh, } }); + MutableSpan corner_verts = mesh.corner_verts_for_write(); + /* Step 2: Calculate vertex fans. */ Array> vertex_fan_sizes(mesh.totvert); threading::parallel_for(IndexRange(mesh.totvert), 512, [&](IndexRange range) { @@ -483,39 +415,43 @@ void split_edges(Mesh &mesh, vert_to_edge_map[vert], vertex_fan_sizes[vert], edge_to_loop_map, - new_edges, corner_verts, new_to_old_verts_map); } }); - /* Step 4: Deduplicate edges. We loop backwards so we can use remove_and_reorder. Although this - * does look bad (3 nested loops), in practice the inner loops are very small. For most meshes, - * there are at most 2 polygons connected to each edge, and hence you'll only get at most 1 - * duplicate per edge. */ - for (int mask_i = mask.size() - 1; mask_i >= 0; mask_i--) { - const int edge = mask[mask_i]; - int start_of_duplicates = edge_offsets[edge]; - int end_of_duplicates = start_of_duplicates + num_edge_duplicates[edge] - 1; - for (int duplicate = end_of_duplicates; duplicate >= start_of_duplicates; duplicate--) { - if (naive_edges_equal(new_edges[edge], new_edges[duplicate])) { - merge_edges( - edge, duplicate, corner_edges, edge_to_loop_map, new_edges, new_to_old_edges_map); - break; - } - for (int other = start_of_duplicates; other < duplicate; other++) { - if (naive_edges_equal(new_edges[other], new_edges[duplicate])) { - merge_edges( - other, duplicate, corner_edges, edge_to_loop_map, new_edges, new_to_old_edges_map); - break; - } - } + VectorSet new_edges; + new_edges.reserve(new_edges_size + loose_edges.size()); + for (const int i : polys.index_range()) { + const IndexRange poly = polys[i]; + for (const int corner : poly) { + const int vert_1 = corner_verts[corner]; + const int vert_2 = corner_verts[bke::mesh::poly_corner_next(poly, corner)]; + corner_edges[corner] = new_edges.index_of_or_add_as(OrderedEdge(vert_1, vert_2)); + } + } + loose_edges.foreach_index([&](const int64_t i) { new_edges.add(OrderedEdge(edges[i])); }); + + Array new_to_old_edges_map(new_edges.size()); + auto index_mask_to_indices = [&](const IndexMask &mask, MutableSpan indices) { + for (const int i : mask.index_range()) { + indices[i] = mask[i]; + } + }; + index_mask_to_indices(loose_edges, + new_to_old_edges_map.as_mutable_span().take_back(loose_edges.size())); + for (const int i : polys.index_range()) { + const IndexRange poly = polys[i]; + for (const int corner : poly) { + const int new_edge_i = corner_edges[corner]; + const int old_edge_i = orig_corner_edges[corner]; + new_to_old_edges_map[new_edge_i] = old_edge_i; } } /* Step 5: Resize the mesh to add the new vertices and rebuild the edges. */ add_new_vertices(mesh, new_to_old_verts_map); - add_new_edges(mesh, new_edges, new_to_old_edges_map, propagation_info); + add_new_edges(mesh, new_edges.as_span().cast(), new_to_old_edges_map, propagation_info); BKE_mesh_tag_edges_split(&mesh); }