From c31718649dba9308cbd252b1150501b5c688a27a Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 27 Nov 2023 10:56:29 -0500 Subject: [PATCH] Geometry Nodes: Rewrite parts of extrude mesh node The node is still a bit non-standard in that it resizes an existing mesh rather than creating a new one, but this commit makes the extrude node a bit more similar to other mesh operations and makes other miscellaneous improvements, including: - Less use of intermediate states (compared to initial or final) - Topology map building is no longer reimplemented for the node - Attribute interpolation happens in a more familiar way - Some topology maps can be skipped if a domain is empty - More use of `IndexMask` instead of an array of indices - Logarithmic cost index mask lookup is avoided - Build index maps instead of implementing attribute propagation separately for every type Overall these changes might improve performance in a few cases, and they reduce Blender's binary size by 58 KB. Edge indices are different in some cases of the edge mode, so the test files are updated. --- .../geometry/nodes/node_geo_extrude_mesh.cc | 941 +++++++++--------- 1 file changed, 474 insertions(+), 467 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc index 6d08c1a8644..a3dc153f8f8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc @@ -15,6 +15,7 @@ #include "BKE_mesh_mapping.hh" #include "BKE_mesh_runtime.hh" +#include "GEO_mesh_selection.hh" #include "GEO_randomize.hh" #include "NOD_rna_define.hh" @@ -74,12 +75,11 @@ struct AttributeOutputs { AnonymousAttributeIDPtr side_id; }; -static void save_selection_as_attribute(Mesh &mesh, +static void save_selection_as_attribute(MutableAttributeAccessor attributes, const AnonymousAttributeID *id, const eAttrDomain domain, const IndexMask &selection) { - MutableAttributeAccessor attributes = mesh.attributes_for_write(); BLI_assert(!attributes.contains(id)); SpanAttributeWriter attribute = attributes.lookup_or_add_for_write_span(id, domain); @@ -207,21 +207,49 @@ static MutableSpan get_orig_index_layer(Mesh &mesh, const eAttrDomain domai return {}; } -/** - * \param get_mix_indices_fn: Returns a Span of indices of the source points to mix for every - * result point. - */ template void copy_with_mixing(const Span src, - const FunctionRef(int)> get_mix_indices_fn, + const GroupedSpan src_groups, + const IndexMask &selection, + MutableSpan dst) +{ + selection.foreach_segment( + GrainSize(512), [&](const IndexMaskSegment segment, const int64_t segment_pos) { + const IndexRange dst_range(segment_pos, segment.size()); + bke::attribute_math::DefaultPropagationMixer mixer{dst.slice(dst_range)}; + for (const int i : segment.index_range()) { + for (const int src_i : src_groups[segment[i]]) { + mixer.mix_in(i, src[src_i]); + } + } + mixer.finalize(); + }); +} + +static void copy_with_mixing(const GSpan src, + const GroupedSpan src_groups, + const IndexMask &selection, + GMutableSpan dst) +{ + BLI_assert(selection.size() == dst.size()); + bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + copy_with_mixing(src.typed(), src_groups, selection, dst.typed()); + }); +} + +template +void copy_with_mixing(const Span src, + const GroupedSpan src_groups, + const Span selection, MutableSpan dst) { threading::parallel_for(dst.index_range(), 512, [&](const IndexRange range) { bke::attribute_math::DefaultPropagationMixer mixer{dst.slice(range)}; - for (const int i_dst : IndexRange(range.size())) { - const Span indices = get_mix_indices_fn(range[i_dst]); - for (const int i_src : indices) { - mixer.mix_in(i_dst, src[i_src]); + for (const int i : range.index_range()) { + const int group_i = selection[i]; + for (const int i_src : src_groups[group_i]) { + mixer.mix_in(i, src[i_src]); } } mixer.finalize(); @@ -229,25 +257,57 @@ void copy_with_mixing(const Span src, } static void copy_with_mixing(const GSpan src, - const FunctionRef(int)> get_mix_indices_fn, + const GroupedSpan src_groups, + const Span selection, GMutableSpan dst) { bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { using T = decltype(dummy); - copy_with_mixing(src.typed(), get_mix_indices_fn, dst.typed()); + copy_with_mixing(src.typed(), src_groups, selection, dst.typed()); }); } -static Array> create_vert_to_edge_map(const int vert_size, - const Span edges, - const int vert_offset = 0) +using IDsByDomain = std::array, ATTR_DOMAIN_NUM>; + +static IDsByDomain attribute_ids_by_domain(const AttributeAccessor attributes, + const Set &skip) { - Array> vert_to_edge_map(vert_size); - for (const int i : edges.index_range()) { - vert_to_edge_map[edges[i][0] - vert_offset].append(i); - vert_to_edge_map[edges[i][1] - vert_offset].append(i); + IDsByDomain map; + attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + if (meta_data.data_type == CD_PROP_STRING) { + return true; + } + if (skip.contains(id.name())) { + return true; + } + map[meta_data.domain].append(id); + return true; + }); + return map; +} + +static void gather_attributes(MutableAttributeAccessor attributes, + const Span ids, + const Span indices, + const IndexRange new_range) +{ + for (const AttributeIDRef &id : ids) { + GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); + bke::attribute_math::gather(attribute.span, indices, attribute.span.slice(new_range)); + attribute.finish(); + } +} + +static void gather_attributes(MutableAttributeAccessor attributes, + const Span ids, + const IndexMask &indices, + const IndexRange new_range) +{ + for (const AttributeIDRef &id : ids) { + GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); + array_utils::gather(attribute.span, indices, attribute.span.slice(new_range)); + attribute.finish(); } - return vert_to_edge_map; } static void extrude_mesh_vertices(Mesh &mesh, @@ -274,58 +334,39 @@ static void extrude_mesh_vertices(Mesh &mesh, MutableAttributeAccessor attributes = mesh.attributes_for_write(); remove_non_propagated_attributes(attributes, propagation_info); - remove_unsupported_vert_data(mesh); - remove_unsupported_edge_data(mesh); - Set point_ids; - Set edge_ids; - mesh.attributes().for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - if (meta_data.data_type == CD_PROP_STRING) { - return true; - } - if (meta_data.domain == ATTR_DOMAIN_POINT) { - if (id.name() != "position") { - point_ids.add(id); - } - } - else if (meta_data.domain == ATTR_DOMAIN_EDGE) { - if (id.name() != ".edge_verts") { - edge_ids.add(id); - } - } - return true; - }); + const IDsByDomain ids_by_domain = attribute_ids_by_domain(attributes, + {"position", ".edge_verts"}); - /* This allows parallelizing attribute mixing for new edges. */ - Array> vert_to_edge_map; - if (!edge_ids.is_empty()) { - vert_to_edge_map = create_vert_to_edge_map(orig_vert_size, mesh.edges()); + Array vert_to_edge_offsets; + Array vert_to_edge_indices; + GroupedSpan vert_to_edge_map; + if (!ids_by_domain[ATTR_DOMAIN_EDGE].is_empty()) { + vert_to_edge_map = bke::mesh::build_vert_to_edge_map( + mesh.edges(), orig_vert_size, vert_to_edge_offsets, vert_to_edge_indices); } + remove_unsupported_vert_data(mesh); + remove_unsupported_edge_data(mesh); expand_mesh(mesh, selection.size(), selection.size(), 0, 0); const IndexRange new_vert_range{orig_vert_size, selection.size()}; const IndexRange new_edge_range{orig_edge_size, selection.size()}; MutableSpan new_edges = mesh.edges_for_write().slice(new_edge_range); - selection.foreach_index_optimized([&](const int index, const int i_selection) { - new_edges[i_selection] = int2(index, new_vert_range[i_selection]); - }); + selection.foreach_index_optimized( + GrainSize(4096), [&](const int index, const int i_selection) { + new_edges[i_selection] = int2(index, new_vert_range[i_selection]); + }); /* New vertices copy the attribute values from their source vertex. */ - for (const AttributeIDRef &id : point_ids) { - GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); - array_utils::gather(attribute.span, selection, attribute.span.slice(new_vert_range)); - attribute.finish(); - } + gather_attributes(attributes, ids_by_domain[ATTR_DOMAIN_POINT], selection, new_vert_range); /* New edge values are mixed from of all the edges connected to the source vertex. */ - for (const AttributeIDRef &id : edge_ids) { + for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_EDGE]) { GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); copy_with_mixing( - attribute.span, - [&](const int i) { return vert_to_edge_map[selection[i]].as_span(); }, - attribute.span.slice(new_edge_range)); + attribute.span, vert_to_edge_map, selection, attribute.span.slice(new_edge_range)); attribute.finish(); } @@ -346,11 +387,11 @@ static void extrude_mesh_vertices(Mesh &mesh, if (attribute_outputs.top_id) { save_selection_as_attribute( - mesh, attribute_outputs.top_id.get(), ATTR_DOMAIN_POINT, new_vert_range); + attributes, attribute_outputs.top_id.get(), ATTR_DOMAIN_POINT, new_vert_range); } if (attribute_outputs.side_id) { save_selection_as_attribute( - mesh, attribute_outputs.side_id.get(), ATTR_DOMAIN_EDGE, new_edge_range); + attributes, attribute_outputs.side_id.get(), ATTR_DOMAIN_EDGE, new_edge_range); } const bool no_loose_vert_hint = mesh.runtime->loose_verts_cache.is_cached() && @@ -408,22 +449,40 @@ static void fill_quad_consistent_direction(const Span other_face_verts, } } -template -static VectorSet vert_indices_from_edges(const Mesh &mesh, const Span edge_indices) +static void create_reverse_map(const IndexMask &mask, MutableSpan r_map) { - static_assert(is_same_any_v); - const Span edges = mesh.edges(); - - VectorSet vert_indices; - vert_indices.reserve(edge_indices.size()); - for (const T i_edge : edge_indices) { - const int2 &edge = edges[i_edge]; - vert_indices.add(edge[0]); - vert_indices.add(edge[1]); - } - return vert_indices; +#ifdef DEBUG + r_map.fill(-1); +#endif + mask.foreach_index_optimized( + GrainSize(4096), [&](const int src_i, const int dst_i) { r_map[src_i] = dst_i; }); } +static GroupedSpan build_vert_to_edge_map(const Span edges, + const IndexMask &edge_mask, + const int verts_num, + Array &r_offsets, + Array &r_indices) +{ + if (edge_mask.size() == edges.size()) { + return bke::mesh::build_vert_to_edge_map(edges, verts_num, r_offsets, r_indices); + } + Array masked_edges(edge_mask.size()); + array_utils::gather(edges, edge_mask, masked_edges.as_mutable_span()); + + bke::mesh::build_vert_to_edge_map(masked_edges, verts_num, r_offsets, r_indices); + + Array masked_edge_to_edge(edge_mask.size()); + edge_mask.to_indices(masked_edge_to_edge); + + threading::parallel_for(r_indices.index_range(), 4096, [&](const IndexRange range) { + for (const int i : range) { + r_indices[i] = masked_edge_to_edge[r_indices[i]]; + } + }); + + return {r_offsets.as_span(), r_indices.as_span()}; +} static void tag_mesh_added_faces(Mesh &mesh) { const bool no_loose_vert_hint = mesh.runtime->loose_verts_cache.is_cached() && @@ -465,11 +524,6 @@ static void extrude_mesh_edges(Mesh &mesh, return; } - Array edge_to_face_offsets; - Array edge_to_face_indices; - const GroupedSpan edge_to_face_map = bke::mesh::build_edge_to_face_map( - orig_faces, mesh.corner_edges(), mesh.totedge, edge_to_face_offsets, edge_to_face_indices); - /* Find the offsets on the vertex domain for translation. This must be done before the mesh's * custom data layers are reallocated, in case the virtual array references one of them. */ Array vert_offsets; @@ -477,7 +531,7 @@ static void extrude_mesh_edges(Mesh &mesh, vert_offsets.reinitialize(orig_vert_size); bke::attribute_math::DefaultPropagationMixer mixer(vert_offsets); edge_selection.foreach_index([&](const int i_edge) { - const int2 &edge = orig_edges[i_edge]; + const int2 edge = orig_edges[i_edge]; const float3 offset = edge_offsets[i_edge]; mixer.mix_in(edge[0], offset); mixer.mix_in(edge[1], offset); @@ -485,12 +539,11 @@ static void extrude_mesh_edges(Mesh &mesh, mixer.finalize(); } - Vector edge_selection_indices(edge_selection.size()); - edge_selection.to_indices(edge_selection_indices.as_mutable_span()); - const VectorSet new_vert_indices = vert_indices_from_edges(mesh, - edge_selection_indices); + IndexMaskMemory memory; + const IndexMask new_verts = geometry::vert_selection_from_edge( + orig_edges, edge_selection, orig_vert_size, memory); - const IndexRange new_vert_range{orig_vert_size, new_vert_indices.size()}; + const IndexRange new_vert_range{orig_vert_size, new_verts.size()}; /* The extruded edges connect the original and duplicate edges. */ const IndexRange connect_edge_range{orig_edges.size(), new_vert_range.size()}; /* The duplicate edges are extruded copies of the selected edges. */ @@ -500,7 +553,25 @@ static void extrude_mesh_edges(Mesh &mesh, /* Every new face is a quad with four corners. */ const IndexRange new_loop_range{orig_loop_size, new_face_range.size() * 4}; - remove_non_propagated_attributes(mesh.attributes_for_write(), propagation_info); + MutableAttributeAccessor attributes = mesh.attributes_for_write(); + remove_non_propagated_attributes(attributes, propagation_info); + + const IDsByDomain ids_by_domain = attribute_ids_by_domain( + attributes, {"position", ".edge_verts", ".corner_vert", ".corner_edge"}); + + Array edge_to_face_offsets; + Array edge_to_face_indices; + const GroupedSpan edge_to_face_map = bke::mesh::build_edge_to_face_map( + orig_faces, mesh.corner_edges(), mesh.totedge, edge_to_face_offsets, edge_to_face_indices); + + Array vert_to_edge_offsets; + Array vert_to_edge_indices; + GroupedSpan vert_to_selected_edge_map; + if (!ids_by_domain[ATTR_DOMAIN_EDGE].is_empty()) { + vert_to_selected_edge_map = build_vert_to_edge_map( + orig_edges, edge_selection, orig_vert_size, vert_to_edge_offsets, vert_to_edge_indices); + } + remove_unsupported_vert_data(mesh); remove_unsupported_edge_data(mesh); remove_unsupported_face_data(mesh); @@ -524,31 +595,36 @@ static void extrude_mesh_edges(Mesh &mesh, offset_indices::fill_constant_group_size(4, orig_loop_size, new_face_offsets); const OffsetIndices faces = mesh.faces(); - for (const int i : connect_edges.index_range()) { - connect_edges[i] = int2(new_vert_indices[i], new_vert_range[i]); - } + new_verts.foreach_index_optimized(GrainSize(4096), [&](const int src, const int dst) { + connect_edges[dst] = int2(src, new_vert_range[dst]); + }); - for (const int i : duplicate_edges.index_range()) { - const int2 &orig_edge = edges[edge_selection[i]]; - const int i_new_vert_1 = new_vert_indices.index_of(orig_edge[0]); - const int i_new_vert_2 = new_vert_indices.index_of(orig_edge[1]); - duplicate_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]); + { + Array vert_to_new_vert(orig_vert_size); + create_reverse_map(new_verts, vert_to_new_vert); + for (const int i : duplicate_edges.index_range()) { + const int2 orig_edge = edges[edge_selection[i]]; + const int i_new_vert_1 = vert_to_new_vert[orig_edge[0]]; + const int i_new_vert_2 = vert_to_new_vert[orig_edge[1]]; + duplicate_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]); + } } edge_selection.foreach_index([&](const int64_t orig_edge_index, const int64_t i) { - const int2 &duplicate_edge = duplicate_edges[i]; + const int2 duplicate_edge = duplicate_edges[i]; const int new_vert_1 = duplicate_edge[0]; const int new_vert_2 = duplicate_edge[1]; const int extrude_index_1 = new_vert_1 - orig_vert_size; const int extrude_index_2 = new_vert_2 - orig_vert_size; + const int2 orig_edge = edges[orig_edge_index]; const Span connected_faces = edge_to_face_map[orig_edge_index]; /* When there was a single face connected to the new face, we can use the old one to keep - * the face direction consistent. When there is more than one connected edge, the new face + * the face direction consistent. When there is more than one connected face, the new face * direction is totally arbitrary and the only goal for the behavior is to be deterministic. */ - Span connected_face_verts = {}; - Span connected_face_edges = {}; + Span connected_face_verts; + Span connected_face_edges; if (connected_faces.size() == 1) { const IndexRange connected_face = faces[connected_faces.first()]; connected_face_verts = corner_verts.slice(connected_face); @@ -558,8 +634,8 @@ static void extrude_mesh_edges(Mesh &mesh, connected_face_edges, new_corner_verts.slice(4 * i, 4), new_corner_edges.slice(4 * i, 4), - new_vert_indices[extrude_index_1], - new_vert_indices[extrude_index_2], + orig_edge[0], + orig_edge[1], new_vert_1, new_vert_2, orig_edge_index, @@ -568,139 +644,108 @@ static void extrude_mesh_edges(Mesh &mesh, connect_edge_range[extrude_index_2]); }); - /* Create a map of indices in the extruded vertices array to all of the indices of edges - * in the duplicate edges array that connect to that vertex. This can be used to simplify the - * mixing of attribute data for the connecting edges. */ - const Array> new_vert_to_duplicate_edge_map = create_vert_to_edge_map( - new_vert_range.size(), duplicate_edges, orig_vert_size); + /* New vertices copy the attribute values from their source vertex. */ + gather_attributes(attributes, ids_by_domain[ATTR_DOMAIN_POINT], new_verts, new_vert_range); - MutableAttributeAccessor attributes = mesh.attributes_for_write(); + /* Edges parallel to original edges copy the edge attributes from the original edges. */ + gather_attributes( + attributes, ids_by_domain[ATTR_DOMAIN_EDGE], edge_selection, duplicate_edge_range); - attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - if (meta_data.data_type == CD_PROP_STRING) { - return true; - } - if (ELEM(id.name(), ".corner_vert", ".corner_edge", ".edge_verts")) { - return true; - } + /* Edges connected to original vertices mix values of selected connected edges. */ + for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_EDGE]) { GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); + copy_with_mixing(attribute.span, + vert_to_selected_edge_map, + new_verts, + attribute.span.slice(connect_edge_range)); + attribute.finish(); + } - switch (attribute.domain) { - case ATTR_DOMAIN_POINT: { - /* New vertices copy the attribute values from their source vertex. */ - bke::attribute_math::gather( - attribute.span, new_vert_indices, attribute.span.slice(new_vert_range)); - break; - } - case ATTR_DOMAIN_EDGE: { - /* Edges parallel to original edges copy the edge attributes from the original edges. */ - GMutableSpan duplicate_data = attribute.span.slice(duplicate_edge_range); - array_utils::gather(attribute.span, edge_selection, duplicate_data); + /* Attribute values for new faces are a mix of values connected to its original edge. */ + for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_FACE]) { + GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); + copy_with_mixing( + attribute.span, edge_to_face_map, edge_selection, attribute.span.slice(new_face_range)); + attribute.finish(); + } - /* Edges connected to original vertices mix values of selected connected edges. */ - copy_with_mixing( - duplicate_data, - [&](const int i) { return new_vert_to_duplicate_edge_map[i].as_span(); }, - attribute.span.slice(connect_edge_range)); - break; - } - case ATTR_DOMAIN_FACE: { - /* Attribute values for new faces are a mix of the values of faces connected to the its - * original edge. */ - copy_with_mixing( - attribute.span, - [&](const int i) { return edge_to_face_map[edge_selection[i]]; }, - attribute.span.slice(new_face_range)); - break; - } - case ATTR_DOMAIN_CORNER: { - /* New corners get the average value of all adjacent corners on original faces connected - * to the original edge of their face. */ - bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - MutableSpan data = attribute.span.typed(); - MutableSpan new_data = data.slice(new_loop_range); - edge_selection.foreach_index( - GrainSize(256), [&](const int64_t orig_edge_index, const int64_t i_edge_selection) { - const Span connected_faces = edge_to_face_map[orig_edge_index]; - if (connected_faces.is_empty()) { - /* If there are no connected faces, there is no corner data to interpolate. */ - new_data.slice(4 * i_edge_selection, 4).fill(T()); - return; + /* New corners get the average value of all adjacent corners on original faces connected + * to the original edge of their face. */ + for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_CORNER]) { + GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); + bke::attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) { + using T = decltype(dummy); + MutableSpan data = attribute.span.typed(); + MutableSpan new_data = data.slice(new_loop_range); + edge_selection.foreach_index( + GrainSize(256), [&](const int64_t orig_edge_index, const int64_t i_edge_selection) { + const Span connected_faces = edge_to_face_map[orig_edge_index]; + if (connected_faces.is_empty()) { + /* If there are no connected faces, there is no corner data to interpolate. */ + new_data.slice(4 * i_edge_selection, 4).fill(T()); + return; + } + + /* Both corners on each vertical edge of the side face get the same value, + * so there are only two unique values to mix. */ + Array side_face_corner_data(2); + bke::attribute_math::DefaultPropagationMixer mixer{side_face_corner_data}; + + const int new_vert_1 = duplicate_edges[i_edge_selection][0]; + const int new_vert_2 = duplicate_edges[i_edge_selection][1]; + const int orig_vert_1 = edges[orig_edge_index][0]; + const int orig_vert_2 = edges[orig_edge_index][1]; + + /* Average the corner data from the corners that share a vertex from the + * faces that share an edge with the extruded edge. */ + for (const int connected_face : connected_faces) { + for (const int i_loop : faces[connected_face]) { + if (corner_verts[i_loop] == orig_vert_1) { + mixer.mix_in(0, data[i_loop]); } - - /* Both corners on each vertical edge of the side face get the same value, - * so there are only two unique values to mix. */ - Array side_face_corner_data(2); - bke::attribute_math::DefaultPropagationMixer mixer{side_face_corner_data}; - - const int2 &duplicate_edge = duplicate_edges[i_edge_selection]; - const int new_vert_1 = duplicate_edge[0]; - const int new_vert_2 = duplicate_edge[1]; - const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size]; - const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size]; - - /* Average the corner data from the corners that share a vertex from the - * faces that share an edge with the extruded edge. */ - for (const int i_connected_face : connected_faces.index_range()) { - const IndexRange connected_face = faces[connected_faces[i_connected_face]]; - for (const int i_loop : IndexRange(connected_face)) { - if (corner_verts[i_loop] == orig_vert_1) { - mixer.mix_in(0, data[i_loop]); - } - if (corner_verts[i_loop] == orig_vert_2) { - mixer.mix_in(1, data[i_loop]); - } - } + if (corner_verts[i_loop] == orig_vert_2) { + mixer.mix_in(1, data[i_loop]); } + } + } - mixer.finalize(); + mixer.finalize(); - /* Instead of replicating the order in #fill_quad_consistent_direction here, it's - * simpler (though probably slower) to just match the corner data based on the - * vertex indices. */ - for (const int i : IndexRange(4 * i_edge_selection, 4)) { - if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) { - new_data[i] = side_face_corner_data.first(); - } - else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) { - new_data[i] = side_face_corner_data.last(); - } - } - }); - }); - break; - } - default: - BLI_assert_unreachable(); - } + /* Instead of replicating the order in #fill_quad_consistent_direction here, it's + * simpler (though probably slower) to just match the corner data based on the + * vertex indices. */ + for (const int i : IndexRange(4 * i_edge_selection, 4)) { + if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) { + new_data[i] = side_face_corner_data.first(); + } + else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) { + new_data[i] = side_face_corner_data.last(); + } + } + }); + }); attribute.finish(); - return true; - }); + } - MutableSpan new_positions = mesh.vert_positions_for_write().slice(new_vert_range); + MutableSpan positions = mesh.vert_positions_for_write(); + MutableSpan new_positions = positions.slice(new_vert_range); if (edge_offsets.is_single()) { const float3 offset = edge_offsets.get_internal_single(); - threading::parallel_for(new_positions.index_range(), 1024, [&](const IndexRange range) { - for (const int i : range) { - new_positions[i] += offset; - } + new_verts.foreach_index_optimized(GrainSize(1024), [&](const int src, const int dst) { + new_positions[dst] = positions[src] + offset; }); } else { - threading::parallel_for(new_positions.index_range(), 1024, [&](const IndexRange range) { - for (const int i : range) { - new_positions[i] += vert_offsets[new_vert_indices[i]]; - } + new_verts.foreach_index_optimized(GrainSize(1024), [&](const int src, const int dst) { + new_positions[dst] = positions[src] + vert_offsets[src]; }); } MutableSpan vert_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_POINT); if (!vert_orig_indices.is_empty()) { - array_utils::gather(vert_orig_indices.as_span(), - new_vert_indices.as_span(), - vert_orig_indices.slice(new_vert_range)); + array_utils::gather( + vert_orig_indices.as_span(), new_verts, vert_orig_indices.slice(new_vert_range)); } MutableSpan edge_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_EDGE); @@ -716,16 +761,30 @@ static void extrude_mesh_edges(Mesh &mesh, if (attribute_outputs.top_id) { save_selection_as_attribute( - mesh, attribute_outputs.top_id.get(), ATTR_DOMAIN_EDGE, duplicate_edge_range); + attributes, attribute_outputs.top_id.get(), ATTR_DOMAIN_EDGE, duplicate_edge_range); } if (attribute_outputs.side_id) { save_selection_as_attribute( - mesh, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, new_face_range); + attributes, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, new_face_range); } tag_mesh_added_faces(mesh); } +static VectorSet vert_indices_from_edges(const Mesh &mesh, const Span edge_indices) +{ + const Span edges = mesh.edges(); + + VectorSet vert_indices; + vert_indices.reserve(edge_indices.size()); + for (const int i_edge : edge_indices) { + const int2 &edge = edges[i_edge]; + vert_indices.add(edge[0]); + vert_indices.add(edge[1]); + } + return vert_indices; +} + /** * Edges connected to one selected face are on the boundary of a region and will be duplicated into * a "side face". Edges inside a region will be duplicated to leave any original faces unchanged. @@ -778,15 +837,10 @@ static void extrude_mesh_face_regions(Mesh &mesh, const GroupedSpan edge_to_face_map = bke::mesh::build_edge_to_face_map( orig_faces, mesh.corner_edges(), mesh.totedge, edge_to_face_offsets, edge_to_face_indices); - /* All vertices that are connected to the selected faces. - * Start the size at one vert per face to reduce unnecessary reallocation. */ - VectorSet all_selected_verts; - all_selected_verts.reserve(orig_faces.size()); - face_selection.foreach_index([&](const int i_face) { - for (const int vert : orig_corner_verts.slice(orig_faces[i_face])) { - all_selected_verts.add(vert); - } - }); + /* All vertices that are connected to the selected faces. */ + IndexMaskMemory memory; + const IndexMask all_selected_verts = geometry::vert_selection_from_face( + orig_faces, face_selection, orig_corner_verts, orig_vert_size, memory); /* Edges inside of an extruded region that are also attached to deselected edges. They must be * duplicated in order to leave the old edge attached to the unchanged deselected faces. */ @@ -835,7 +889,7 @@ static void extrude_mesh_face_regions(Mesh &mesh, } } - VectorSet new_vert_indices = vert_indices_from_edges(mesh, boundary_edge_indices.as_span()); + VectorSet new_vert_indices = vert_indices_from_edges(mesh, boundary_edge_indices); /* Before adding the rest of the new vertices from the new inner edges, store the number * of new vertices from the boundary edges, since this is the number of connecting edges. */ const int extruded_vert_size = new_vert_indices.size(); @@ -860,7 +914,12 @@ static void extrude_mesh_face_regions(Mesh &mesh, /* The loops that form the new side faces. */ const IndexRange side_loop_range{orig_corner_verts.size(), side_face_range.size() * 4}; - remove_non_propagated_attributes(mesh.attributes_for_write(), propagation_info); + MutableAttributeAccessor attributes = mesh.attributes_for_write(); + remove_non_propagated_attributes(attributes, propagation_info); + + const IDsByDomain ids_by_domain = attribute_ids_by_domain( + attributes, {".corner_vert", ".corner_edge", ".edge_verts"}); + remove_unsupported_vert_data(mesh); remove_unsupported_edge_data(mesh); remove_unsupported_face_data(mesh); @@ -966,107 +1025,87 @@ static void extrude_mesh_face_regions(Mesh &mesh, connect_edge_range[extrude_index_2]); } - /* Create a map of indices in the extruded vertices array to all of the indices of edges - * in the duplicate edges array that connect to that vertex. This can be used to simplify the - * mixing of attribute data for the connecting edges. */ - const Array> new_vert_to_duplicate_edge_map = create_vert_to_edge_map( - new_vert_range.size(), boundary_edges, orig_vert_size); + /* New vertices copy the attributes from their original vertices. */ + gather_attributes( + attributes, ids_by_domain[ATTR_DOMAIN_POINT], new_vert_indices, new_vert_range); - MutableAttributeAccessor attributes = mesh.attributes_for_write(); + /* New faces on the side of extrusions get the values from the corresponding selected face. */ + gather_attributes( + attributes, ids_by_domain[ATTR_DOMAIN_FACE], edge_extruded_face_indices, side_face_range); - attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - if (meta_data.data_type == CD_PROP_STRING) { - return true; + if (!ids_by_domain[ATTR_DOMAIN_EDGE].is_empty()) { + IndexMaskMemory memory; + const IndexMask boundary_edge_mask = IndexMask::from_indices(boundary_edge_indices, + memory); + + Array vert_to_edge_offsets; + Array vert_to_edge_indices; + const GroupedSpan vert_to_boundary_edge_map = build_vert_to_edge_map( + edges, boundary_edge_mask, mesh.totvert, vert_to_edge_offsets, vert_to_edge_indices); + + for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_EDGE]) { + GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); + + /* Edges parallel to original edges copy the edge attributes from the original edges. */ + GMutableSpan boundary_data = attribute.span.slice(boundary_edge_range); + array_utils::gather(attribute.span, boundary_edge_mask, boundary_data); + + /* Edges inside of face regions also just duplicate their source data. */ + GMutableSpan new_inner_data = attribute.span.slice(new_inner_edge_range); + bke::attribute_math::gather(attribute.span, new_inner_edge_indices, new_inner_data); + + /* Edges connected to original vertices mix values of selected connected edges. */ + copy_with_mixing(attribute.span, + vert_to_boundary_edge_map, + new_vert_indices, + attribute.span.slice(connect_edge_range)); + attribute.finish(); } - if (ELEM(id.name(), ".corner_vert", ".corner_edge", ".edge_verts")) { - return true; - } - GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); + } - switch (attribute.domain) { - case ATTR_DOMAIN_POINT: { - /* New vertices copy the attributes from their original vertices. */ - bke::attribute_math::gather( - attribute.span, new_vert_indices, attribute.span.slice(new_vert_range)); - break; + /* New corners get the values from the corresponding corner on the extruded face. */ + if (!ids_by_domain[ATTR_DOMAIN_CORNER].is_empty()) { + Array orig_corners(side_loop_range.size()); + threading::parallel_for(boundary_edge_indices.index_range(), 256, [&](const IndexRange range) { + for (const int i_boundary_edge : range) { + const int2 &boundary_edge = boundary_edges[i_boundary_edge]; + const int new_vert_1 = boundary_edge[0]; + const int new_vert_2 = boundary_edge[1]; + const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size]; + const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size]; + + /* Retrieve the data for the first two sides of the quad from the extruded + * face, which we generally expect to have just a small amount of sides. This + * loop could be eliminated by adding a cache of connected loops (which would + * also simplify some of the other code to find the correct loops on the extruded + * face). */ + int corner_1; + int corner_2; + for (const int corner : faces[edge_extruded_face_indices[i_boundary_edge]]) { + if (corner_verts[corner] == new_vert_1) { + corner_1 = corner; + } + if (corner_verts[corner] == new_vert_2) { + corner_2 = corner; + } + } + + /* Instead of replicating the order in #fill_quad_consistent_direction here, it's + * simpler (though probably slower) to just match the corner data based on the + * vertex indices. */ + for (const int i : IndexRange(4 * i_boundary_edge, 4)) { + if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) { + orig_corners[i] = corner_1; + } + else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) { + orig_corners[i] = corner_2; + } + } } - case ATTR_DOMAIN_EDGE: { - /* Edges parallel to original edges copy the edge attributes from the original edges. */ - GMutableSpan boundary_data = attribute.span.slice(boundary_edge_range); - bke::attribute_math::gather(attribute.span, boundary_edge_indices, boundary_data); - - /* Edges inside of face regions also just duplicate their source data. */ - GMutableSpan new_inner_data = attribute.span.slice(new_inner_edge_range); - bke::attribute_math::gather(attribute.span, new_inner_edge_indices, new_inner_data); - - /* Edges connected to original vertices mix values of selected connected edges. */ - copy_with_mixing( - boundary_data, - [&](const int i) { return new_vert_to_duplicate_edge_map[i].as_span(); }, - attribute.span.slice(connect_edge_range)); - break; - } - case ATTR_DOMAIN_FACE: { - /* New faces on the side of extrusions get the values from the corresponding selected - * face. */ - GMutableSpan side_data = attribute.span.slice(side_face_range); - bke::attribute_math::gather(attribute.span, edge_extruded_face_indices, side_data); - break; - } - case ATTR_DOMAIN_CORNER: { - /* New corners get the values from the corresponding corner on the extruded face. */ - bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - MutableSpan data = attribute.span.typed(); - MutableSpan new_data = data.slice(side_loop_range); - threading::parallel_for( - boundary_edge_indices.index_range(), 256, [&](const IndexRange range) { - for (const int i_boundary_edge : range) { - const int2 &boundary_edge = boundary_edges[i_boundary_edge]; - const int new_vert_1 = boundary_edge[0]; - const int new_vert_2 = boundary_edge[1]; - const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size]; - const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size]; - - /* Retrieve the data for the first two sides of the quad from the extruded - * face, which we generally expect to have just a small amount of sides. This - * loop could be eliminated by adding a cache of connected loops (which would - * also simplify some of the other code to find the correct loops on the extruded - * face). */ - T data_1; - T data_2; - for (const int i_loop : faces[edge_extruded_face_indices[i_boundary_edge]]) { - if (corner_verts[i_loop] == new_vert_1) { - data_1 = data[i_loop]; - } - if (corner_verts[i_loop] == new_vert_2) { - data_2 = data[i_loop]; - } - } - - /* Instead of replicating the order in #fill_quad_consistent_direction here, it's - * simpler (though probably slower) to just match the corner data based on the - * vertex indices. */ - for (const int i : IndexRange(4 * i_boundary_edge, 4)) { - if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) { - new_data[i] = data_1; - } - else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) { - new_data[i] = data_2; - } - } - } - }); - }); - break; - } - default: - BLI_assert_unreachable(); - } - - attribute.finish(); - return true; - }); + }); + gather_attributes( + attributes, ids_by_domain[ATTR_DOMAIN_CORNER], orig_corners, side_loop_range); + } /* Translate vertices based on the offset. If the vertex is used by a selected edge, it will * have been duplicated and only the new vertex should use the offset. Otherwise the vertex might @@ -1074,33 +1113,27 @@ static void extrude_mesh_face_regions(Mesh &mesh, MutableSpan positions = mesh.vert_positions_for_write(); if (face_position_offsets.is_single()) { const float3 offset = face_position_offsets.get_internal_single(); - threading::parallel_for( - IndexRange(all_selected_verts.size()), 1024, [&](const IndexRange range) { - for (const int i_orig : all_selected_verts.as_span().slice(range)) { - const int i_new = new_vert_indices.index_of_try(i_orig); - if (i_new == -1) { - positions[i_orig] += offset; - } - else { - positions[new_vert_range[i_new]] += offset; - } - } - }); + all_selected_verts.foreach_index(GrainSize(1024), [&](const int orig_vert) { + const int i_new = new_vert_indices.index_of_try(orig_vert); + if (i_new == -1) { + positions[orig_vert] += offset; + } + else { + positions[new_vert_range[i_new]] += offset; + } + }); } else { - threading::parallel_for( - IndexRange(all_selected_verts.size()), 1024, [&](const IndexRange range) { - for (const int i_orig : all_selected_verts.as_span().slice(range)) { - const int i_new = new_vert_indices.index_of_try(i_orig); - const float3 offset = vert_offsets[i_orig]; - if (i_new == -1) { - positions[i_orig] += offset; - } - else { - positions[new_vert_range[i_new]] += offset; - } - } - }); + all_selected_verts.foreach_index(GrainSize(1024), [&](const int orig_vert) { + const int i_new = new_vert_indices.index_of_try(orig_vert); + const float3 offset = vert_offsets[orig_vert]; + if (i_new == -1) { + positions[orig_vert] += offset; + } + else { + positions[new_vert_range[i_new]] += offset; + } + }); } MutableSpan vert_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_POINT); @@ -1130,11 +1163,11 @@ static void extrude_mesh_face_regions(Mesh &mesh, if (attribute_outputs.top_id) { save_selection_as_attribute( - mesh, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, face_selection); + attributes, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, face_selection); } if (attribute_outputs.side_id) { save_selection_as_attribute( - mesh, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_face_range); + attributes, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_face_range); } tag_mesh_added_faces(mesh); @@ -1169,15 +1202,10 @@ static void extrude_individual_mesh_faces( /* Build an array of offsets into the new data for each face. This is used to facilitate * parallelism later on by avoiding the need to keep track of an offset when iterating through * all faces. */ - int extrude_corner_size = 0; Array group_per_face_data(face_selection.size() + 1); - face_selection.foreach_index_optimized([&](const int index, const int i_selection) { - group_per_face_data[i_selection] = extrude_corner_size; - extrude_corner_size += orig_faces[index].size(); - }); - - group_per_face_data.last() = extrude_corner_size; - const OffsetIndices group_per_face(group_per_face_data); + const OffsetIndices group_per_face = offset_indices::gather_selected_offsets( + orig_faces, face_selection, group_per_face_data); + const int extrude_corner_size = group_per_face.total_size(); const IndexRange new_vert_range{orig_vert_size, extrude_corner_size}; /* One edge connects each selected vertex to a new vertex on the extruded faces. */ @@ -1188,7 +1216,12 @@ static void extrude_individual_mesh_faces( const IndexRange side_face_range{orig_faces.size(), duplicate_edge_range.size()}; const IndexRange side_loop_range{orig_loop_size, side_face_range.size() * 4}; - remove_non_propagated_attributes(mesh.attributes_for_write(), propagation_info); + MutableAttributeAccessor attributes = mesh.attributes_for_write(); + remove_non_propagated_attributes(attributes, propagation_info); + + const IDsByDomain ids_by_domain = attribute_ids_by_domain( + attributes, {"position", ".edge_verts", ".corner_vert", ".corner_edge"}); + remove_unsupported_vert_data(mesh); remove_unsupported_edge_data(mesh); remove_unsupported_face_data(mesh); @@ -1199,7 +1232,8 @@ static void extrude_individual_mesh_faces( side_face_range.size(), side_loop_range.size()); - MutableSpan new_positions = mesh.vert_positions_for_write().slice(new_vert_range); + MutableSpan positions = mesh.vert_positions_for_write(); + MutableSpan new_positions = positions.slice(new_vert_range); MutableSpan edges = mesh.edges_for_write(); MutableSpan connect_edges = edges.slice(connect_edge_range); MutableSpan duplicate_edges = edges.slice(duplicate_edge_range); @@ -1267,119 +1301,94 @@ static void extrude_individual_mesh_faces( } }); - MutableAttributeAccessor attributes = mesh.attributes_for_write(); + /* New vertices copy the attributes from their original vertices. */ + gather_attributes( + attributes, ids_by_domain[ATTR_DOMAIN_POINT], new_vert_indices, new_vert_range); - attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - if (meta_data.data_type == CD_PROP_STRING) { - return true; - } - if (ELEM(id.name(), ".corner_vert", ".corner_edge", ".edge_verts")) { - return true; + /* The data for the duplicate edge is simply a copy of the original edge's data. */ + gather_attributes( + attributes, ids_by_domain[ATTR_DOMAIN_EDGE], duplicate_edge_indices, duplicate_edge_range); + + /* For extruded edges, mix the data from the two neighboring original edges of the face. */ + if (!ids_by_domain[ATTR_DOMAIN_EDGE].is_empty()) { + Array neighbor_edges(connect_edge_range.size()); + face_selection.foreach_index( + GrainSize(1024), [&](const int64_t index, const int64_t i_selection) { + const IndexRange face = faces[index]; + const IndexRange extrude_range = group_per_face[i_selection]; + + for (const int i : face.index_range()) { + const int i_prev = (i == 0) ? face.size() - 1 : i - 1; + const int i_extrude = extrude_range[i]; + const int i_extrude_prev = extrude_range[i_prev]; + neighbor_edges[i_extrude] = int2(duplicate_edge_indices[i_extrude], + duplicate_edge_indices[i_extrude_prev]); + } + }); + + for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_EDGE]) { + GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); + bke::attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) { + using T = decltype(dummy); + MutableSpan data = attribute.span.typed(); + MutableSpan dst = data.slice(connect_edge_range); + threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) { + for (const int i : range) { + const int2 neighbors = neighbor_edges[i]; + if constexpr (std::is_same_v) { + /* Propagate selections with "or" instead of "at least half". */ + dst[i] = data[neighbors[0]] || data[neighbors[1]]; + } + else { + dst[i] = bke::attribute_math::mix2(0.5f, data[neighbors[0]], data[neighbors[1]]); + } + } + }); + }); + attribute.finish(); } + } + + /* Each side face gets the values from the corresponding new face. */ + for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_FACE]) { GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); - - switch (attribute.domain) { - case ATTR_DOMAIN_POINT: { - /* New vertices copy the attributes from their original vertices. */ - GMutableSpan new_data = attribute.span.slice(new_vert_range); - bke::attribute_math::gather(attribute.span, new_vert_indices, new_data); - break; - } - case ATTR_DOMAIN_EDGE: { - /* The data for the duplicate edge is simply a copy of the original edge's data. */ - GMutableSpan duplicate_data = attribute.span.slice(duplicate_edge_range); - bke::attribute_math::gather(attribute.span, duplicate_edge_indices, duplicate_data); - - bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - MutableSpan data = attribute.span.typed(); - MutableSpan connect_data = data.slice(connect_edge_range); - face_selection.foreach_index( - GrainSize(512), [&](const int64_t index, const int64_t i_selection) { - const IndexRange face = faces[index]; - const IndexRange extrude_range = group_per_face[i_selection]; - - /* For the extruded edges, mix the data from the two neighboring original edges of - * the extruded face. */ - for (const int i : face.index_range()) { - const int i_prev = (i == 0) ? face.size() - 1 : i - 1; - const int i_extrude = extrude_range[i]; - const int i_extrude_prev = extrude_range[i_prev]; - - const int orig_edge = duplicate_edge_indices[i_extrude]; - const int orig_edge_prev = duplicate_edge_indices[i_extrude_prev]; - if constexpr (std::is_same_v) { - /* Propagate selections with "or" instead of "at least half". */ - connect_data[i_extrude] = data[orig_edge] || data[orig_edge_prev]; - } - else { - connect_data[i_extrude] = bke::attribute_math::mix2( - 0.5f, data[orig_edge], data[orig_edge_prev]); - } - } - }); - }); - break; - } - case ATTR_DOMAIN_FACE: { - /* Each side face gets the values from the corresponding new face. */ - bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - MutableSpan data = attribute.span.typed(); - MutableSpan new_data = data.slice(side_face_range); - face_selection.foreach_index( - GrainSize(1024), [&](const int64_t face_index, const int64_t i_selection) { - const IndexRange extrude_range = group_per_face[i_selection]; - new_data.slice(extrude_range).fill(data[face_index]); - }); - }); - break; - } - case ATTR_DOMAIN_CORNER: { - /* Each corner on a side face gets its value from the matching corner on an extruded - * face. */ - bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - MutableSpan data = attribute.span.typed(); - MutableSpan new_data = data.slice(side_loop_range); - face_selection.foreach_index( - GrainSize(256), [&](const int64_t index, const int64_t i_selection) { - const IndexRange face = faces[index]; - const Span face_loop_data = data.slice(face); - const IndexRange extrude_range = group_per_face[i_selection]; - - for (const int i : face.index_range()) { - const int i_next = (i == face.size() - 1) ? 0 : i + 1; - const int i_extrude = extrude_range[i]; - - MutableSpan side_loop_data = new_data.slice(i_extrude * 4, 4); - - /* The two corners on each side of the side face get the data from the - * matching corners of the extruded face. This order depends on the loop - * filling the loop indices. */ - side_loop_data[0] = face_loop_data[i_next]; - side_loop_data[1] = face_loop_data[i]; - side_loop_data[2] = face_loop_data[i]; - side_loop_data[3] = face_loop_data[i_next]; - } - }); - }); - break; - } - default: - BLI_assert_unreachable(); - } - + bke::attribute_math::gather_to_groups( + group_per_face, face_selection, attribute.span, attribute.span.slice(side_face_range)); attribute.finish(); - return true; - }); + } + + /* Each corner on a side face gets its value from the matching corner on an extruded face. */ + if (!ids_by_domain[ATTR_DOMAIN_CORNER].is_empty()) { + Array orig_corners(side_loop_range.size()); + face_selection.foreach_index( + GrainSize(256), [&](const int64_t index, const int64_t i_selection) { + const IndexRange face = faces[index]; + const IndexRange extrude_range = group_per_face[i_selection]; + + for (const int i : face.index_range()) { + const IndexRange side_face(extrude_range[i] * 4, 4); + /* The two corners on each side of the side face get the data from the + * matching corners of the extruded face. This order depends on the loop + * filling the loop indices. */ + const int corner = face[i]; + const int next_corner = bke::mesh::face_corner_next(face, corner); + orig_corners[side_face[0]] = next_corner; + orig_corners[side_face[1]] = corner; + orig_corners[side_face[2]] = corner; + orig_corners[side_face[3]] = next_corner; + } + }); + gather_attributes( + attributes, ids_by_domain[ATTR_DOMAIN_CORNER], orig_corners, side_loop_range); + } /* Offset the new vertices. */ face_selection.foreach_index(GrainSize(1025), [&](const int64_t index, const int64_t i_selection) { const IndexRange extrude_range = group_per_face[i_selection]; - for (float3 &position : new_positions.slice(extrude_range)) { - position += face_offset[index]; + for (const int i : extrude_range) { + const int src_vert = new_vert_indices[i]; + new_positions[i] = positions[src_vert] + face_offset[index]; } }); @@ -1400,21 +1409,19 @@ static void extrude_individual_mesh_faces( MutableSpan face_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_FACE); if (!face_orig_indices.is_empty()) { - MutableSpan new_face_orig_indices = face_orig_indices.slice(side_face_range); - face_selection.foreach_index( - GrainSize(1024), [&](const int64_t face_i, const int64_t selection_i) { - const IndexRange extrude_range = group_per_face[selection_i]; - new_face_orig_indices.slice(extrude_range).fill(face_orig_indices[face_i]); - }); + array_utils::gather_to_groups(group_per_face, + face_selection, + face_orig_indices.as_span(), + face_orig_indices.slice(side_face_range)); } if (attribute_outputs.top_id) { save_selection_as_attribute( - mesh, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, face_selection); + attributes, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, face_selection); } if (attribute_outputs.side_id) { save_selection_as_attribute( - mesh, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_face_range); + attributes, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_face_range); } tag_mesh_added_faces(mesh);