Files
test/source/blender/geometry/intern/mesh_split_edges.cc
Hans Goudey b20ecee555 Mesh: Move freestyle tags to generic attributes
This commit moves the freestyle edge and face mark tags to become
generic attributes, similar to other changes over the past years. The
attributes are called "freestyle_edge" and "freestyle_face", and they're
now propagated like regular boolean attributes.

Compatibility wise, forward and backward blend file compatibility are
maintained (for forward compatibility this is implemented a bit
differently than in the past because of the ongoing `AttributeStorage`
transition). In the Python API, `use_freestyle_mark` has been removed;
the attribute API should be used instead (just like bevel weights).
The BMesh (`freestyle`) accessors are removed too.

The conversions benefit from the fact that bit-wise, the old structs are
the same as `bool`, so we can convert to the old and new formats without
reallocating arrays.

Pull Request: https://projects.blender.org/blender/blender/pulls/141996
2025-07-16 18:26:26 +02:00

593 lines
24 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_index_mask.hh"
#include "BLI_ordered_edge.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
#include "BKE_customdata.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_mapping.hh"
#include "GEO_mesh_selection.hh"
#include "GEO_mesh_split_edges.hh"
#include "GEO_randomize.hh"
namespace blender::geometry {
static void propagate_vert_attributes(Mesh &mesh, const Span<int> new_to_old_verts_map)
{
/* These types aren't supported for interpolation below. */
CustomData_free_layers(&mesh.vert_data, CD_SHAPEKEY);
CustomData_free_layers(&mesh.vert_data, CD_CLOTH_ORCO);
CustomData_free_layers(&mesh.vert_data, CD_MVERT_SKIN);
CustomData_realloc(
&mesh.vert_data, mesh.verts_num, mesh.verts_num + new_to_old_verts_map.size());
mesh.verts_num += new_to_old_verts_map.size();
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
for (const StringRef id : attributes.all_ids()) {
const bke::AttributeMetaData meta_data = *attributes.lookup_meta_data(id);
if (meta_data.domain != bke::AttrDomain::Point) {
continue;
}
if (meta_data.data_type == bke::AttrType::String) {
continue;
}
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
if (!attribute) {
continue;
}
bke::attribute_math::gather(attribute.span,
new_to_old_verts_map,
attribute.span.take_back(new_to_old_verts_map.size()));
attribute.finish();
}
if (float3 *orco = static_cast<float3 *>(
CustomData_get_layer_for_write(&mesh.vert_data, CD_ORCO, mesh.verts_num)))
{
array_utils::gather(Span(orco, mesh.verts_num),
new_to_old_verts_map,
MutableSpan(orco, mesh.verts_num).take_back(new_to_old_verts_map.size()));
}
if (int *orig_indices = static_cast<int *>(
CustomData_get_layer_for_write(&mesh.vert_data, CD_ORIGINDEX, mesh.verts_num)))
{
array_utils::gather(
Span(orig_indices, mesh.verts_num),
new_to_old_verts_map,
MutableSpan(orig_indices, mesh.verts_num).take_back(new_to_old_verts_map.size()));
}
}
static void propagate_edge_attributes(Mesh &mesh, const Span<int> new_to_old_edge_map)
{
CustomData_realloc(&mesh.edge_data, mesh.edges_num, mesh.edges_num + new_to_old_edge_map.size());
mesh.edges_num += new_to_old_edge_map.size();
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
for (const StringRef id : attributes.all_ids()) {
const bke::AttributeMetaData meta_data = *attributes.lookup_meta_data(id);
if (meta_data.domain != bke::AttrDomain::Edge) {
continue;
}
if (meta_data.data_type == bke::AttrType::String) {
continue;
}
if (id == ".edge_verts") {
/* Edge vertices are updated and combined with new edges separately. */
continue;
}
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
if (!attribute) {
continue;
}
bke::attribute_math::gather(
attribute.span, new_to_old_edge_map, attribute.span.take_back(new_to_old_edge_map.size()));
attribute.finish();
}
if (int *orig_indices = static_cast<int *>(
CustomData_get_layer_for_write(&mesh.edge_data, CD_ORIGINDEX, mesh.edges_num)))
{
array_utils::gather(
Span(orig_indices, mesh.edges_num),
new_to_old_edge_map,
MutableSpan(orig_indices, mesh.edges_num).take_back(new_to_old_edge_map.size()));
}
}
/**
* Used for fanning around the corners connected to a vertex.
*
* Depending on the winding direction of neighboring faces, traveling from a corner across an edge
* to a different face can give a corner that uses a different vertex than the original. To find
* the face's corner that uses the original vertex, we may have to use the next corner instead.
*/
static int corner_on_edge_connected_to_vert(const Span<int> corner_verts,
const int corner,
const IndexRange face,
const int vert)
{
if (corner_verts[corner] == vert) {
return corner;
}
const int other = bke::mesh::face_corner_next(face, corner);
BLI_assert(corner_verts[other] == vert);
return other;
}
using CornerGroup = Vector<int>;
/**
* Collect groups of corners connected by edges bordered by boundary edges or split edges. We store
* corner indices instead of edge indices because later on in the algorithm we only relink the
* `corner_vert` array to each group's new vertex.
*
* The corners are not ordered in winding order, since we only need to group connected faces into
* each group.
*/
static Vector<CornerGroup> calc_corner_groups_for_vertex(const OffsetIndices<int> faces,
const Span<int> corner_verts,
const Span<int> corner_edges,
const GroupedSpan<int> edge_to_corner_map,
const Span<int> corner_to_face_map,
const BitSpan split_edges,
const Span<int> connected_corners,
const int vert)
{
Vector<CornerGroup> groups;
/* Each corner should only be added to a single group. */
BitVector<> used_corners(connected_corners.size());
for (const int start_corner : connected_corners) {
CornerGroup group;
Vector<int> corner_stack({start_corner});
while (!corner_stack.is_empty()) {
const int corner = corner_stack.pop_last();
const int i = connected_corners.first_index(corner);
if (used_corners[i]) {
continue;
}
used_corners[i].set();
group.append(corner);
const int face = corner_to_face_map[corner];
const int prev_corner = bke::mesh::face_corner_prev(faces[face], corner);
/* Travel across the two edges neighboring this vertex, if they aren't split. */
for (const int edge : {corner_edges[corner], corner_edges[prev_corner]}) {
if (split_edges[edge]) {
continue;
}
for (const int other_corner : edge_to_corner_map[edge]) {
const int other_face = corner_to_face_map[other_corner];
if (other_face == face) {
/* Avoid continuing back to the same face. */
continue;
}
const int neighbor_corner = corner_on_edge_connected_to_vert(
corner_verts, other_corner, faces[other_face], vert);
corner_stack.append(neighbor_corner);
}
}
}
if (!group.is_empty()) {
groups.append(std::move(group));
}
}
return groups;
}
/* Calculate groups of corners that are contiguously connected to each input vertex.
* BLI_NOINLINE because MSVC 17.7 has a codegen bug here, given there is only a single call to this
* function, not inlining it for all platforms won't affect performance. See
* https://developercommunity.visualstudio.com/t/10448291 for details. */
BLI_NOINLINE static Array<Vector<CornerGroup>> calc_all_corner_groups(
const OffsetIndices<int> faces,
const Span<int> corner_verts,
const Span<int> corner_edges,
const GroupedSpan<int> vert_to_corner_map,
const GroupedSpan<int> edge_to_corner_map,
const Span<int> corner_to_face_map,
const BitSpan split_edges,
const IndexMask &affected_verts)
{
Array<Vector<CornerGroup>> corner_groups(affected_verts.size(), NoInitialization());
affected_verts.foreach_index(GrainSize(512), [&](const int vert, const int mask) {
new (&corner_groups[mask])
Vector<CornerGroup>(calc_corner_groups_for_vertex(faces,
corner_verts,
corner_edges,
edge_to_corner_map,
corner_to_face_map,
split_edges,
vert_to_corner_map[vert],
vert));
});
return corner_groups;
}
/** Selected and unselected loose edges attached to a vertex. */
struct VertLooseEdges {
Vector<int> selected;
Vector<int> unselected;
};
/** Find selected and non-selected loose edges connected to a vertex. */
static VertLooseEdges calc_vert_loose_edges(const GroupedSpan<int> vert_to_edge_map,
const BitSpan loose_edges,
const BitSpan split_edges,
const int vert)
{
VertLooseEdges info;
for (const int edge : vert_to_edge_map[vert]) {
if (loose_edges[edge]) {
if (split_edges[edge]) {
info.selected.append(edge);
}
else {
info.unselected.append(edge);
}
}
}
return info;
}
/**
* Every affected vertex maps to potentially multiple output vertices. Create a mapping from
* affected vertex index to the group of output vertex indices (indices are within those groups,
* not indices in arrays of _all_ vertices). For every original vertex, reuse the original vertex
* for the first of:
* 1. The last face corner group
* 2. The last selected loose edge
* 3. The group of non-selected loose edges
* Using this order prioritizes the simplicity of the no-loose-edge case, which we assume is
* more common.
*/
static OffsetIndices<int> calc_vert_ranges_per_old_vert(
const IndexMask &affected_verts,
const Span<Vector<CornerGroup>> corner_groups,
const GroupedSpan<int> vert_to_edge_map,
const BitSpan loose_edges,
const BitSpan split_edges,
Array<int> &offset_data)
{
offset_data.reinitialize(affected_verts.size() + 1);
MutableSpan<int> new_verts_nums = offset_data;
threading::parallel_for(affected_verts.index_range(), 2048, [&](const IndexRange range) {
/* Start with -1 for the reused vertex. None of the final sizes should be negative. */
new_verts_nums.slice(range).fill(-1);
for (const int i : range) {
new_verts_nums[i] += corner_groups[i].size();
}
});
if (!loose_edges.is_empty()) {
affected_verts.foreach_index(GrainSize(512), [&](const int vert, const int mask) {
const VertLooseEdges info = calc_vert_loose_edges(
vert_to_edge_map, loose_edges, split_edges, vert);
new_verts_nums[mask] += info.selected.size();
if (corner_groups[mask].is_empty()) {
/* Loose edges share their vertex with a corner group if possible. */
new_verts_nums[mask] += info.unselected.size() > 0;
}
});
}
return offset_indices::accumulate_counts_to_offsets(offset_data);
}
/**
* Update corner verts so that each group of corners gets its own vertex. For the last "new vertex"
* we can reuse the original vertex, which would otherwise become unused by any faces. The loose
* edge case will have to deal with this later.
*/
static void update_corner_verts(const int orig_verts_num,
const Span<Vector<CornerGroup>> corner_groups,
const OffsetIndices<int> new_verts_by_affected_vert,
MutableSpan<int> new_corner_verts)
{
threading::parallel_for(corner_groups.index_range(), 512, [&](const IndexRange range) {
for (const int new_vert : range) {
const Span<CornerGroup> groups = corner_groups[new_vert];
const IndexRange new_verts = new_verts_by_affected_vert[new_vert];
for (const int group : groups.index_range().drop_back(1)) {
const int new_vert = orig_verts_num + new_verts[group];
new_corner_verts.fill_indices(groups[group].as_span(), new_vert);
}
}
});
}
static OrderedEdge edge_from_corner(const OffsetIndices<int> faces,
const Span<int> corner_verts,
const Span<int> corner_to_face_map,
const int corner)
{
const int face = corner_to_face_map[corner];
const int corner_next = bke::mesh::face_corner_next(faces[face], corner);
return OrderedEdge(corner_verts[corner], corner_verts[corner_next]);
}
/**
* Based on updated corner vertex indices, update the edges in each face. This includes updating
* corner edge indices, adding new edges, and reusing original edges for the first "split" edge.
* The main complexity comes from the fact that in the case of single isolated split edges, no new
* edges are created because they all end up identical. We need to handle this case, but since it's
* rare, we optimize for the case that it doesn't happen first.
*/
static Array<int2> calc_new_edges(const OffsetIndices<int> faces,
const Span<int> corner_verts,
const GroupedSpan<int> edge_to_corner_map,
const Span<int> corner_to_face_map,
const IndexMask &selected_edges,
MutableSpan<int2> edges,
MutableSpan<int> corner_edges,
MutableSpan<int> r_new_edge_offsets)
{
/* Calculate the offset of new edges assuming no new edges are identical and are merged. */
selected_edges.foreach_index_optimized<int>(
GrainSize(4096), [&](const int edge, const int mask) {
r_new_edge_offsets[mask] = std::max<int>(edge_to_corner_map[edge].size() - 1, 0);
});
const OffsetIndices offsets = offset_indices::accumulate_counts_to_offsets(r_new_edge_offsets);
Array<int2> new_edges(offsets.total_size());
/* Count the number of final new edges per edge, to use as offsets if there are duplicates. */
Array<int> num_edges_per_edge_merged(r_new_edge_offsets.size());
std::atomic<bool> found_duplicate = false;
/* The first new edge for each selected edge is reused-- we modify the existing edge in
* place. Simply reusing the first new edge isn't enough because deduplication might make
* multiple new edges reuse the original. */
Array<bool> is_reused(corner_verts.size(), false);
/* Calculate per-original split edge deduplication of new edges, which are stored by the
* corner vertices of connected faces. Update corner verts to store the updated indices. */
selected_edges.foreach_index(GrainSize(1024), [&](const int edge, const int mask) {
if (edge_to_corner_map[edge].is_empty()) {
/* Handle loose edges. */
num_edges_per_edge_merged[mask] = 0;
return;
}
const int new_edges_start = offsets[mask].start();
Vector<OrderedEdge> deduplication;
for (const int corner : edge_to_corner_map[edge]) {
const OrderedEdge edge = edge_from_corner(faces, corner_verts, corner_to_face_map, corner);
int index = deduplication.first_index_of_try(edge);
if (UNLIKELY(index != -1)) {
found_duplicate.store(true, std::memory_order_relaxed);
}
else {
index = deduplication.append_and_get_index(edge);
}
if (index == 0) {
is_reused[corner] = true;
}
else {
corner_edges[corner] = edges.size() + new_edges_start + index - 1;
}
}
const int new_edges_num = deduplication.size() - 1;
edges[edge] = int2(deduplication.first().v_low, deduplication.first().v_high);
new_edges.as_mutable_span()
.slice(new_edges_start, new_edges_num)
.copy_from(deduplication.as_span().drop_front(1).cast<int2>());
num_edges_per_edge_merged[mask] = new_edges_num;
});
if (!found_duplicate) {
/* No edges were merged, we can use the existing output array and offsets. */
return new_edges;
}
/* Update corner edges to remove the "holes" left by merged new edges. */
const OffsetIndices offsets_merged = offset_indices::accumulate_counts_to_offsets(
num_edges_per_edge_merged);
selected_edges.foreach_index(GrainSize(2048), [&](const int edge, const int mask) {
const int difference = offsets[mask].start() - offsets_merged[mask].start();
for (const int corner : edge_to_corner_map[edge]) {
if (!is_reused[corner]) {
corner_edges[corner] -= difference;
}
}
});
/* Create new edges without the empty slots for the duplicates */
Array<int2> new_edges_merged(offsets_merged.total_size());
threading::parallel_for(offsets_merged.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
new_edges_merged.as_mutable_span()
.slice(offsets_merged[i])
.copy_from(new_edges.as_span().slice(offsets[i].start(), offsets_merged[i].size()));
}
});
r_new_edge_offsets.copy_from(num_edges_per_edge_merged);
return new_edges_merged;
}
static void update_unselected_edges(const OffsetIndices<int> faces,
const Span<int> corner_verts,
const GroupedSpan<int> edge_to_corner_map,
const Span<int> corner_to_face_map,
const IndexMask &unselected_edges,
MutableSpan<int2> edges)
{
unselected_edges.foreach_index(GrainSize(1024), [&](const int edge) {
const Span<int> edge_corners = edge_to_corner_map[edge];
if (edge_corners.is_empty()) {
return;
}
const int corner = edge_corners.first();
const OrderedEdge new_edge = edge_from_corner(faces, corner_verts, corner_to_face_map, corner);
edges[edge] = int2(new_edge.v_low, new_edge.v_high);
});
}
static void swap_edge_vert(int2 &edge, const int old_vert, const int new_vert)
{
if (edge[0] == old_vert) {
edge[0] = new_vert;
}
else if (edge[1] == old_vert) {
edge[1] = new_vert;
}
}
/**
* Assign the newly created vertex duplicates to the loose edges around this vertex. Every split
* loose edge is reattached to a newly created vertex. If there are non-split loose edges attached
* to the vertex, they all reuse the original vertex.
*/
static void reassign_loose_edge_verts(const int orig_verts_num,
const IndexMask &affected_verts,
const GroupedSpan<int> vert_to_edge_map,
const BitSpan loose_edges,
const BitSpan split_edges,
const Span<Vector<CornerGroup>> corner_groups,
const OffsetIndices<int> new_verts_by_affected_vert,
MutableSpan<int2> edges)
{
affected_verts.foreach_index(GrainSize(1024), [&](const int vert, const int mask) {
const IndexRange new_verts = new_verts_by_affected_vert[mask];
/* Account for the reuse of the original vertex by non-loose corner groups. In practice this
* means using the new vertices for each split loose edge until we run out of new vertices.
* We then expect the count to match up with the number of new vertices reserved by
* #calc_vert_ranges_per_old_vert. */
int new_vert_i = std::max<int>(corner_groups[mask].size() - 1, 0);
if (new_vert_i == new_verts.size()) {
return;
}
const VertLooseEdges vert_info = calc_vert_loose_edges(
vert_to_edge_map, loose_edges, split_edges, vert);
for (const int edge : vert_info.selected) {
const int new_vert = orig_verts_num + new_verts[new_vert_i];
swap_edge_vert(edges[edge], vert, new_vert);
new_vert_i++;
if (new_vert_i == new_verts.size()) {
return;
}
}
const int new_vert = orig_verts_num + new_verts[new_vert_i];
for (const int orig_edge : vert_info.unselected) {
swap_edge_vert(edges[orig_edge], vert, new_vert);
}
});
}
/**
* Transform the #OffsetIndices storage of new elements per source element into a more
* standard index map which can be used with existing utilities to copy attributes.
*/
static Array<int> offsets_to_map(const IndexMask &mask, const OffsetIndices<int> offsets)
{
Array<int> map(offsets.total_size());
mask.foreach_index(GrainSize(1024), [&](const int i, const int mask) {
map.as_mutable_span().slice(offsets[mask]).fill(i);
});
return map;
}
void split_edges(Mesh &mesh,
const IndexMask &selected_edges,
const bke::AttributeFilter & /*attribute_filter*/)
{
const int orig_verts_num = mesh.verts_num;
const Span<int2> orig_edges = mesh.edges();
const OffsetIndices faces = mesh.faces();
IndexMaskMemory memory;
const IndexMask affected_verts = vert_selection_from_edge(
orig_edges, selected_edges, orig_verts_num, memory);
BitVector<> selection_bits(orig_edges.size());
selected_edges.to_bits(selection_bits);
const bke::LooseEdgeCache &loose_edges = mesh.loose_edges();
const GroupedSpan<int> vert_to_corner_map = mesh.vert_to_corner_map();
Array<int> edge_to_corner_offsets;
Array<int> edge_to_corner_indices;
const GroupedSpan<int> edge_to_corner_map = bke::mesh::build_edge_to_corner_map(
mesh.corner_edges(), orig_edges.size(), edge_to_corner_offsets, edge_to_corner_indices);
Array<int> vert_to_edge_offsets;
Array<int> vert_to_edge_indices;
GroupedSpan<int> vert_to_edge_map;
if (loose_edges.count > 0) {
vert_to_edge_map = bke::mesh::build_vert_to_edge_map(
orig_edges, orig_verts_num, vert_to_edge_offsets, vert_to_edge_indices);
}
const Array<int> corner_to_face_map = mesh.corner_to_face_map();
const Array<Vector<CornerGroup>> corner_groups = calc_all_corner_groups(faces,
mesh.corner_verts(),
mesh.corner_edges(),
vert_to_corner_map,
edge_to_corner_map,
corner_to_face_map,
selection_bits,
affected_verts);
Array<int> vert_new_vert_offset_data;
const OffsetIndices new_verts_by_affected_vert = calc_vert_ranges_per_old_vert(
affected_verts,
corner_groups,
vert_to_edge_map,
loose_edges.is_loose_bits,
selection_bits,
vert_new_vert_offset_data);
MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
update_corner_verts(orig_verts_num, corner_groups, new_verts_by_affected_vert, corner_verts);
Array<int> new_edge_offsets(selected_edges.size() + 1);
Array<int2> new_edges = calc_new_edges(faces,
corner_verts,
edge_to_corner_map,
corner_to_face_map,
selected_edges,
mesh.edges_for_write(),
mesh.corner_edges_for_write(),
new_edge_offsets);
const IndexMask unselected_edges = selected_edges.complement(orig_edges.index_range(), memory);
update_unselected_edges(faces,
corner_verts,
edge_to_corner_map,
corner_to_face_map,
unselected_edges,
mesh.edges_for_write());
if (loose_edges.count > 0) {
reassign_loose_edge_verts(orig_verts_num,
affected_verts,
vert_to_edge_map,
loose_edges.is_loose_bits,
selection_bits,
corner_groups,
new_verts_by_affected_vert,
mesh.edges_for_write());
}
const Array<int> edge_map = offsets_to_map(selected_edges, new_edge_offsets.as_span());
propagate_edge_attributes(mesh, edge_map);
mesh.edges_for_write().take_back(new_edges.size()).copy_from(new_edges);
const Array<int> vert_map = offsets_to_map(affected_verts, new_verts_by_affected_vert);
propagate_vert_attributes(mesh, vert_map);
mesh.tag_edges_split();
debug_randomize_vert_order(&mesh);
debug_randomize_edge_order(&mesh);
}
} // namespace blender::geometry