diff --git a/source/blender/blenkernel/BKE_mesh_mapping.h b/source/blender/blenkernel/BKE_mesh_mapping.h index c5c81b31b79..a0ded44f630 100644 --- a/source/blender/blenkernel/BKE_mesh_mapping.h +++ b/source/blender/blenkernel/BKE_mesh_mapping.h @@ -352,11 +352,21 @@ Array build_loop_to_poly_map(Span polys, int loops_num); Array> build_vert_to_edge_map(Span edges, int verts_num); Array> build_vert_to_loop_map(Span loops, int verts_num); +Array> build_edge_to_loop_map(Span loops, int edges_num); +Vector> build_edge_to_loop_map_resizable(Span loops, int edges_num); inline int previous_poly_loop(const MPoly &poly, int loop_i) { return loop_i - 1 + (loop_i == poly.loopstart) * poly.totloop; } +inline int next_poly_loop(const MPoly &poly, int loop_i) +{ + if (loop_i == poly.loopstart + poly.totloop - 1) { + return poly.loopstart; + } + return loop_i + 1; +} + } // namespace blender::bke::mesh_topology #endif diff --git a/source/blender/blenkernel/intern/mesh_mapping.cc b/source/blender/blenkernel/intern/mesh_mapping.cc index ed4ae94da7f..98fb8a7fb42 100644 --- a/source/blender/blenkernel/intern/mesh_mapping.cc +++ b/source/blender/blenkernel/intern/mesh_mapping.cc @@ -586,6 +586,24 @@ Array> build_vert_to_loop_map(const Span loops, const int ver return map; } +Array> build_edge_to_loop_map(const Span loops, const int edges_num) +{ + Array> map(edges_num); + for (const int64_t i : loops.index_range()) { + map[loops[i].e].append(int(i)); + } + return map; +} + +Vector> build_edge_to_loop_map_resizable(const Span loops, const int edges_num) +{ + Vector> map(edges_num); + for (const int64_t i : loops.index_range()) { + map[loops[i].e].append(int(i)); + } + return map; +} + } // namespace blender::bke::mesh_topology /** \} */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc index 0b4d5bd53f3..1a19897a148 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -1,13 +1,15 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BLI_array_utils.hh" +#include "BLI_task.hh" + #include "DNA_mesh_types.h" +#include "BKE_attribute_math.hh" #include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" #include "BKE_mesh_runtime.h" -#include "bmesh.h" -#include "bmesh_tools.h" - #include "node_geometry_util.hh" namespace blender::nodes::node_geo_edge_split_cc { @@ -19,30 +21,410 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Mesh")); } -static Mesh *mesh_edge_split(const Mesh &mesh, const IndexMask selection) +/* Naively checks if the first vertices and the second vertices are the same. */ +static inline bool naive_edges_equal(const MEdge &edge1, const MEdge &edge2) { - BMeshCreateParams bmesh_create_params{}; - bmesh_create_params.use_toolflags = true; - const BMAllocTemplate allocsize = {0, 0, 0, 0}; - BMesh *bm = BM_mesh_create(&allocsize, &bmesh_create_params); + return edge1.v1 == edge2.v1 && edge1.v2 == edge2.v2; +} - BMeshFromMeshParams bmesh_from_mesh_params{}; - bmesh_from_mesh_params.cd_mask_extra.vmask = CD_MASK_ORIGINDEX; - bmesh_from_mesh_params.cd_mask_extra.emask = CD_MASK_ORIGINDEX; - bmesh_from_mesh_params.cd_mask_extra.pmask = CD_MASK_ORIGINDEX; - BM_mesh_bm_from_me(bm, &mesh, &bmesh_from_mesh_params); +static void transfer_attributes(const Map &attributes, + const Span new_to_old_verts_map, + const Span new_to_old_edges_map, + const AttributeAccessor src_attributes, + MutableAttributeAccessor dst_attributes) +{ + for (Map::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + GAttributeReader src_attribute = src_attributes.lookup(attribute_id); + if (!src_attribute) { + continue; + } + const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( + src_attribute.varray.type()); + GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, src_attribute.domain, data_type); + if (!dst_attribute) { + continue; + } - BM_mesh_elem_table_ensure(bm, BM_EDGE); - for (const int i : selection) { - BMEdge *edge = BM_edge_at_index(bm, i); - BM_elem_flag_enable(edge, BM_ELEM_TAG); + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + VArraySpan span{src_attribute.varray.typed()}; + MutableSpan dst_span = dst_attribute.span.typed(); + switch (src_attribute.domain) { + case ATTR_DOMAIN_POINT: + array_utils::gather(span, new_to_old_verts_map, dst_span); + break; + case ATTR_DOMAIN_EDGE: + array_utils::gather(span, new_to_old_edges_map, dst_span); + break; + case ATTR_DOMAIN_FACE: + dst_span.copy_from(span); + break; + case ATTR_DOMAIN_CORNER: + dst_span.copy_from(span); + break; + default: + BLI_assert_unreachable(); + } + }); + dst_attribute.finish(); + } +} + +/** + * 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_loops, + 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_loops[loop_i].e = 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_loops[last_edge_loop_i].e = new_edge_i; } - BM_mesh_edgesplit(bm, false, true, false); + /* 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); +} - Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, &mesh); - BM_mesh_free(bm); +/** + * 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(MEdge &edge, + const int old_vert, + const int new_vert, + MutableSpan loops, + const Span connected_loops) +{ + if (edge.v1 == old_vert) { + edge.v1 = new_vert; + } + else if (edge.v2 == old_vert) { + edge.v2 = new_vert; + } + else { + BLI_assert_unreachable(); + } + for (const int loop_i : connected_loops) { + if (loops[loop_i].v == old_vert) { + loops[loop_i].v = 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, + const Span fans, + const Span fan_sizes, + const Span> edge_to_loop_map, + MutableSpan new_edges, + MutableSpan new_loops, + MutableSpan new_to_old_verts_map) +{ + int fan_start = 0; + /* We don't need to create a new vertex for the last fan. That fan can just be connected to the + * original vertex. */ + for (const int i : fan_sizes.index_range().drop_back(1)) { + const int new_vert_i = start_offset + i; + new_to_old_verts_map[new_vert_i] = 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, new_loops, edge_to_loop_map[edge_i]); + } + fan_start += fan_sizes[i]; + } +} + +/** + * Get the index of the adjacent edge to a loop connected to a vertex. In other words, for the + * given polygon return the unique edge connected to the given vertex and not on the given loop. + */ +static int adjacent_edge(Span loops, const int loop_i, const MPoly &poly, const int vertex) +{ + const int adjacent_loop_i = (loops[loop_i].v == vertex) ? + bke::mesh_topology::previous_poly_loop(poly, loop_i) : + bke::mesh_topology::next_poly_loop(poly, loop_i); + return loops[adjacent_loop_i].e; +} + +/** + * Calculate the disjoint fans connected to the vertex, where a fan is a group of edges connected + * through polygons. The connected_edges vector is rearranged in such a way that edges in the same + * fan are grouped together. The r_fans_sizes Vector gives the sizes of the different fans, and can + * be used to retreive the fans from connected_edges. + */ +static void calc_vertex_fans(const int vertex, + const Span new_loops, + const Span polys, + const Span> edge_to_loop_map, + const Span loop_to_poly_map, + MutableSpan connected_edges, + Vector &r_fan_sizes) +{ + if (connected_edges.size() <= 1) { + r_fan_sizes.append(connected_edges.size()); + return; + } + + Vector search_edges; + int total_found_edges_num = 0; + int fan_size = 0; + const int total_edge_num = connected_edges.size(); + /* Iteratively go through the connected edges. The front contains already handled edges, while + * the back contains unhandled edges. */ + while (true) { + /* This edge has not been visited yet. */ + int curr_i = total_found_edges_num; + int curr_edge_i = connected_edges[curr_i]; + + /* Gather all the edges in this fan. */ + while (true) { + fan_size++; + + /* 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_loops, loop_i, polys[loop_to_poly_map[loop_i]], vertex); + + /* Find out if this edge was visited already. */ + int i = curr_i + 1; + for (; i < total_edge_num; i++) { + if (connected_edges[i] == adjacent_edge_i) { + break; + } + } + if (i == total_edge_num) { + /* Already visited this edge. */ + continue; + } + search_edges.append(adjacent_edge_i); + curr_i++; + std::swap(connected_edges[curr_i], connected_edges[i]); + } + + if (search_edges.is_empty()) { + break; + } + + curr_edge_i = search_edges.pop_last(); + } + /* We have now collected all the edges in this fan. */ + total_found_edges_num += fan_size; + BLI_assert(total_found_edges_num <= total_edge_num); + r_fan_sizes.append(fan_size); + if (total_found_edges_num == total_edge_num) { + /* We have found all the edges, so this final batch must be the last connected fan. */ + break; + } + fan_size = 0; + } +} + +/** + * Splits the edge into duplicates, so that each edge is connected to one poly. + */ +static void split_edge_per_poly(const int edge_i, + const int new_edge_start, + MutableSpan> edge_to_loop_map, + MutableSpan new_loops, + MutableSpan new_edges, + MutableSpan new_to_old_edges_map) +{ + 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 MEdge 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}); + new_loops[loop_i].e = new_edge_index; + new_edge_index++; + } + /* Only the first loop is now connected to this edge. */ + edge_to_loop_map[edge_i].resize(1); +} + +static Mesh *mesh_edge_split(const Mesh &mesh, + const IndexMask mask, + const Map &attributes) +{ + /* Flag vertices that need to be split. */ + Array should_split_vert(mesh.totvert, false); + const Span edges = mesh.edges(); + for (const int edge_i : mask) { + const MEdge edge = edges[edge_i]; + should_split_vert[edge.v1] = true; + should_split_vert[edge.v2] = true; + } + + /* Precalculate topology info. */ + Array> vert_to_edge_map = bke::mesh_topology::build_vert_to_edge_map(edges, + mesh.totvert); + Vector> edge_to_loop_map = bke::mesh_topology::build_edge_to_loop_map_resizable( + mesh.loops(), mesh.totedge); + Array loop_to_poly_map = bke::mesh_topology::build_loop_to_poly_map(mesh.polys(), + mesh.totloop); + + /* Store offsets, so we can split edges in parallel. */ + Array edge_offsets(edges.size()); + Array num_edge_duplicates(edges.size()); + int new_edges_size = edges.size(); + for (const int edge : mask) { + edge_offsets[edge] = new_edges_size; + /* We add duplicates of the edge for each poly (except the first). */ + const int num_connected_loops = edge_to_loop_map[edge].size(); + const int num_duplicates = std::max(0, num_connected_loops - 1); + new_edges_size += num_duplicates; + num_edge_duplicates[edge] = num_duplicates; + } + + const Span polys = mesh.polys(); + + Array new_loops(mesh.loops()); + Vector new_edges(new_edges_size); + new_edges.as_mutable_span().take_front(edges.size()).copy_from(edges); + + edge_to_loop_map.resize(new_edges_size); + + /* Used for transferring attributes. */ + Vector new_to_old_verts_map(IndexRange(mesh.totvert).as_span()); + Vector new_to_old_edges_map(IndexRange(new_edges.size()).as_span()); + + /* 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, + new_loops, + new_edges, + new_to_old_edges_map); + } + }); + + /* Step 1.5: Update topology information (can't parallelize). */ + for (const int edge_i : mask) { + const MEdge &edge = edges[edge_i]; + for (const int duplicate_i : IndexRange(edge_offsets[edge_i], num_edge_duplicates[edge_i])) { + vert_to_edge_map[edge.v1].append(duplicate_i); + vert_to_edge_map[edge.v2].append(duplicate_i); + } + } + + /* Step 2: Calculate vertex fans. */ + Array> vertex_fan_sizes(mesh.totvert); + threading::parallel_for(IndexRange(mesh.totvert), 512, [&](IndexRange range) { + for (const int vert : range) { + if (!should_split_vert[vert]) { + continue; + } + calc_vertex_fans(vert, + new_loops, + polys, + edge_to_loop_map, + loop_to_poly_map, + vert_to_edge_map[vert], + vertex_fan_sizes[vert]); + } + }); + + /* Step 2.5: Calculate offsets for next step. */ + Array vert_offsets(mesh.totvert); + int new_verts_size = mesh.totvert; + for (const int vert : IndexRange(mesh.totvert)) { + if (!should_split_vert[vert]) { + continue; + } + vert_offsets[vert] = new_verts_size; + /* We only create a new vertex for each fan different from the first. */ + new_verts_size += vertex_fan_sizes[vert].size() - 1; + } + new_to_old_verts_map.resize(new_verts_size); + + /* Step 3: Split the vertices. */ + threading::parallel_for(IndexRange(mesh.totvert), 512, [&](IndexRange range) { + for (const int vert : range) { + if (!should_split_vert[vert]) { + continue; + } + split_vertex_per_fan(vert, + vert_offsets[vert], + vert_to_edge_map[vert], + vertex_fan_sizes[vert], + edge_to_loop_map, + new_edges, + new_loops, + 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, new_loops, 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, new_loops, edge_to_loop_map, new_edges, new_to_old_edges_map); + break; + } + } + } + } + + /* Copy to the new mesh. */ + Mesh *result = BKE_mesh_new_nomain( + new_verts_size, new_edges.size(), 0, mesh.totloop, mesh.totpoly); + transfer_attributes(attributes, + new_to_old_verts_map, + new_to_old_edges_map, + mesh.attributes(), + result->attributes_for_write()); + + MutableSpan dst_edges = result->edges_for_write(); + dst_edges.copy_from(new_edges); + MutableSpan dst_loops = result->loops_for_write(); + dst_loops.copy_from(new_loops); + MutableSpan dst_polys = result->polys_for_write(); + dst_polys.copy_from(polys); return result; } @@ -53,16 +435,19 @@ static void node_geo_exec(GeoNodeExecParams params) const Field selection_field = params.extract_input>("Selection"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - if (const Mesh *mesh = geometry_set.get_mesh_for_write()) { + if (const Mesh *mesh = geometry_set.get_mesh_for_read()) { bke::MeshFieldContext field_context{*mesh, ATTR_DOMAIN_EDGE}; fn::FieldEvaluator selection_evaluator{field_context, mesh->totedge}; selection_evaluator.add(selection_field); selection_evaluator.evaluate(); - const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + const IndexMask mask = selection_evaluator.get_evaluated_as_mask(0); - Mesh *result = mesh_edge_split(*mesh, selection); + Map attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_MESH, false, attributes); + Mesh *result = mesh_edge_split(*mesh, mask, attributes); geometry_set.replace_mesh(result); } }); diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index ea568bb7c56..e05219901ce 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -795,6 +795,7 @@ set(geo_node_tests mesh_primitives mesh mesh/extrude + mesh/split_edges points utilities vector