For improving performance in the triangulate node (#112264), it's helpful to know whether the mesh has edges or faces that overlap each other. If that is known to be false, the node can skip edge de- duplication.This might apply to the future "Replace Faces" node too. This information is stored as a flag on the mesh and set in various places that create "clean" new meshes. It isn't calculated lazily unlike other other areas, because the point is to improve performance, and the calculation probably isn't faster than the duplication check it's meant to replace. Pull Request: https://projects.blender.org/blender/blender/pulls/113205
571 lines
22 KiB
C++
571 lines
22 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BLI_enumerable_thread_specific.hh"
|
|
#include "BLI_index_mask.hh"
|
|
#include "BLI_listbase.h"
|
|
|
|
#include "BKE_attribute.hh"
|
|
#include "BKE_geometry_fields.hh"
|
|
#include "BKE_mesh.hh"
|
|
|
|
#include "GEO_mesh_copy_selection.hh"
|
|
|
|
namespace blender::geometry {
|
|
|
|
static void create_reverse_map(const IndexMask &mask, MutableSpan<int> r_map)
|
|
{
|
|
#ifdef DEBUG
|
|
r_map.fill(-1);
|
|
#endif
|
|
mask.foreach_index_optimized<int>(
|
|
GrainSize(4096), [&](const int src_i, const int dst_i) { r_map[src_i] = dst_i; });
|
|
}
|
|
|
|
static void remap_verts(const OffsetIndices<int> src_faces,
|
|
const OffsetIndices<int> dst_faces,
|
|
const int src_verts_num,
|
|
const IndexMask &vert_mask,
|
|
const IndexMask &edge_mask,
|
|
const IndexMask &face_mask,
|
|
const Span<int2> src_edges,
|
|
const Span<int> src_corner_verts,
|
|
MutableSpan<int2> dst_edges,
|
|
MutableSpan<int> dst_corner_verts)
|
|
{
|
|
Array<int> map(src_verts_num);
|
|
create_reverse_map(vert_mask, map);
|
|
threading::parallel_invoke(
|
|
vert_mask.size() > 1024,
|
|
[&]() {
|
|
face_mask.foreach_index(GrainSize(512), [&](const int64_t src_i, const int64_t dst_i) {
|
|
const IndexRange src_face = src_faces[src_i];
|
|
const IndexRange dst_face = dst_faces[dst_i];
|
|
for (const int i : src_face.index_range()) {
|
|
dst_corner_verts[dst_face[i]] = map[src_corner_verts[src_face[i]]];
|
|
}
|
|
});
|
|
},
|
|
[&]() {
|
|
edge_mask.foreach_index(GrainSize(512), [&](const int64_t src_i, const int64_t dst_i) {
|
|
dst_edges[dst_i][0] = map[src_edges[src_i][0]];
|
|
dst_edges[dst_i][1] = map[src_edges[src_i][1]];
|
|
});
|
|
});
|
|
}
|
|
|
|
static void remap_edges(const OffsetIndices<int> src_faces,
|
|
const OffsetIndices<int> dst_faces,
|
|
const int src_edges_num,
|
|
const IndexMask &edge_mask,
|
|
const IndexMask &face_mask,
|
|
const Span<int> src_corner_edges,
|
|
MutableSpan<int> dst_corner_edges)
|
|
{
|
|
Array<int> map(src_edges_num);
|
|
create_reverse_map(edge_mask, map);
|
|
face_mask.foreach_index(GrainSize(512), [&](const int64_t src_i, const int64_t dst_i) {
|
|
const IndexRange src_face = src_faces[src_i];
|
|
const IndexRange dst_face = dst_faces[dst_i];
|
|
for (const int i : src_face.index_range()) {
|
|
dst_corner_edges[dst_face[i]] = map[src_corner_edges[src_face[i]]];
|
|
}
|
|
});
|
|
}
|
|
|
|
/** A vertex is selected if it's used by a selected edge. */
|
|
static IndexMask vert_selection_from_edge(const Span<int2> edges,
|
|
const IndexMask &edge_mask,
|
|
const int verts_num,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
Array<bool> array(verts_num, false);
|
|
edge_mask.foreach_index_optimized<int>(GrainSize(4096), [&](const int i) {
|
|
array[edges[i][0]] = true;
|
|
array[edges[i][1]] = true;
|
|
});
|
|
return IndexMask::from_bools(array, memory);
|
|
}
|
|
|
|
static IndexMask mapped_corner_selection_from_face(const OffsetIndices<int> faces,
|
|
const IndexMask &face_mask,
|
|
const Span<int> corner_verts_or_edges,
|
|
const int verts_or_edges_num,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
Array<bool> array(verts_or_edges_num, false);
|
|
face_mask.foreach_index(GrainSize(512), [&](const int64_t i) {
|
|
array.as_mutable_span().fill_indices(corner_verts_or_edges.slice(faces[i]), true);
|
|
});
|
|
return IndexMask::from_bools(array, memory);
|
|
}
|
|
|
|
/** A vertex is selected if it is used by a selected face. */
|
|
static IndexMask vert_selection_from_face(const OffsetIndices<int> faces,
|
|
const IndexMask &face_mask,
|
|
const Span<int> corner_verts,
|
|
const int verts_num,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
return mapped_corner_selection_from_face(faces, face_mask, corner_verts, verts_num, memory);
|
|
}
|
|
|
|
/** An edge is selected if it is used by a selected face. */
|
|
static IndexMask edge_selection_from_face(const OffsetIndices<int> faces,
|
|
const IndexMask &face_mask,
|
|
const Span<int> corner_edges,
|
|
const int edges_num,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
return mapped_corner_selection_from_face(faces, face_mask, corner_edges, edges_num, memory);
|
|
}
|
|
|
|
/** An edge is selected if both of its vertices are selected. */
|
|
static IndexMask edge_selection_from_vert(const Span<int2> edges,
|
|
const Span<bool> vert_selection,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
return IndexMask::from_predicate(
|
|
edges.index_range(), GrainSize(1024), memory, [&](const int64_t i) {
|
|
const int2 edge = edges[i];
|
|
return vert_selection[edge[0]] && vert_selection[edge[1]];
|
|
});
|
|
}
|
|
|
|
static IndexMask face_selection_from_mapped_corner(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts_or_edges,
|
|
const Span<bool> vert_or_edge_selection,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
return IndexMask::from_predicate(
|
|
faces.index_range(), GrainSize(1024), memory, [&](const int64_t i) {
|
|
const Span<int> indices = corner_verts_or_edges.slice(faces[i]);
|
|
return std::all_of(indices.begin(), indices.end(), [&](const int i) {
|
|
return vert_or_edge_selection[i];
|
|
});
|
|
});
|
|
}
|
|
|
|
/** A face is selected if all of its vertices are selected. */
|
|
static IndexMask face_selection_from_vert(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts,
|
|
const Span<bool> vert_selection,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
return face_selection_from_mapped_corner(faces, corner_verts, vert_selection, memory);
|
|
}
|
|
|
|
/** A face is selected if all of its edges are selected. */
|
|
static IndexMask face_selection_from_edge(const OffsetIndices<int> faces,
|
|
const Span<int> corner_edges,
|
|
const Span<bool> edge_mask,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
return face_selection_from_mapped_corner(faces, corner_edges, edge_mask, memory);
|
|
}
|
|
|
|
/** Create a mesh with no built-in attributes. */
|
|
static Mesh *create_mesh_no_attributes(const Mesh ¶ms_mesh,
|
|
const int verts_num,
|
|
const int edges_num,
|
|
const int faces_num,
|
|
const int corners_num)
|
|
{
|
|
Mesh *mesh = BKE_mesh_new_nomain(0, 0, faces_num, 0);
|
|
mesh->totvert = verts_num;
|
|
mesh->totedge = edges_num;
|
|
mesh->totloop = corners_num;
|
|
CustomData_free_layer_named(&mesh->vert_data, "position", 0);
|
|
CustomData_free_layer_named(&mesh->edge_data, ".edge_verts", 0);
|
|
CustomData_free_layer_named(&mesh->loop_data, ".corner_vert", 0);
|
|
CustomData_free_layer_named(&mesh->loop_data, ".corner_edge", 0);
|
|
BKE_mesh_copy_parameters_for_eval(mesh, ¶ms_mesh);
|
|
return mesh;
|
|
}
|
|
|
|
static void copy_loose_vert_hint(const Mesh &src, Mesh &dst)
|
|
{
|
|
const auto &src_cache = src.runtime->loose_verts_cache;
|
|
if (src_cache.is_cached() && src_cache.data().count == 0) {
|
|
dst.tag_loose_verts_none();
|
|
}
|
|
}
|
|
|
|
static void copy_loose_edge_hint(const Mesh &src, Mesh &dst)
|
|
{
|
|
const auto &src_cache = src.runtime->loose_edges_cache;
|
|
if (src_cache.is_cached() && src_cache.data().count == 0) {
|
|
dst.tag_loose_edges_none();
|
|
}
|
|
}
|
|
|
|
static void copy_overlapping_hint(const Mesh &src, Mesh &dst)
|
|
{
|
|
if (src.no_overlapping_topology()) {
|
|
dst.tag_overlapping_none();
|
|
}
|
|
}
|
|
|
|
/** Gather vertex group data and array attributes in separate loops. */
|
|
static void gather_vert_attributes(const Mesh &mesh_src,
|
|
const bke::AnonymousAttributePropagationInfo &propagation_info,
|
|
const IndexMask &vert_mask,
|
|
Mesh &mesh_dst)
|
|
{
|
|
Set<std::string> vertex_group_names;
|
|
LISTBASE_FOREACH (bDeformGroup *, group, &mesh_src.vertex_group_names) {
|
|
vertex_group_names.add(group->name);
|
|
}
|
|
|
|
const Span<MDeformVert> src = mesh_src.deform_verts();
|
|
MutableSpan<MDeformVert> dst = mesh_dst.deform_verts_for_write();
|
|
threading::parallel_invoke(
|
|
src.size() > 1024,
|
|
[&]() {
|
|
if (!src.is_empty() && !dst.is_empty()) {
|
|
vert_mask.foreach_index(GrainSize(512), [&](const int64_t src_i, const int64_t dst_i) {
|
|
dst[dst_i].dw = static_cast<MDeformWeight *>(MEM_dupallocN(src[src_i].dw));
|
|
dst[dst_i].totweight = src[src_i].totweight;
|
|
dst[dst_i].flag = src[src_i].flag;
|
|
});
|
|
}
|
|
},
|
|
[&]() {
|
|
bke::gather_attributes(mesh_src.attributes(),
|
|
ATTR_DOMAIN_POINT,
|
|
propagation_info,
|
|
vertex_group_names,
|
|
vert_mask,
|
|
mesh_dst.attributes_for_write());
|
|
});
|
|
}
|
|
|
|
std::optional<Mesh *> mesh_copy_selection(
|
|
const Mesh &src_mesh,
|
|
const VArray<bool> &selection,
|
|
const eAttrDomain selection_domain,
|
|
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
|
{
|
|
const Span<int2> src_edges = src_mesh.edges();
|
|
const OffsetIndices src_faces = src_mesh.faces();
|
|
const Span<int> src_corner_verts = src_mesh.corner_verts();
|
|
const Span<int> src_corner_edges = src_mesh.corner_edges();
|
|
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
|
|
|
|
if (selection.is_empty()) {
|
|
return std::nullopt;
|
|
}
|
|
if (const std::optional<bool> single = selection.get_if_single()) {
|
|
return *single ? std::nullopt : std::make_optional<Mesh *>(nullptr);
|
|
}
|
|
|
|
threading::EnumerableThreadSpecific<IndexMaskMemory> memory;
|
|
IndexMask vert_mask;
|
|
IndexMask edge_mask;
|
|
IndexMask face_mask;
|
|
switch (selection_domain) {
|
|
case ATTR_DOMAIN_POINT: {
|
|
const VArraySpan<bool> span(selection);
|
|
threading::parallel_invoke(
|
|
src_mesh.totvert > 1024,
|
|
[&]() { vert_mask = IndexMask::from_bools(span, memory.local()); },
|
|
[&]() { edge_mask = edge_selection_from_vert(src_edges, span, memory.local()); },
|
|
[&]() {
|
|
face_mask = face_selection_from_vert(
|
|
src_faces, src_corner_verts, span, memory.local());
|
|
});
|
|
break;
|
|
}
|
|
case ATTR_DOMAIN_EDGE: {
|
|
const VArraySpan<bool> span(selection);
|
|
threading::parallel_invoke(
|
|
src_edges.size() > 1024,
|
|
[&]() {
|
|
edge_mask = IndexMask::from_bools(span, memory.local());
|
|
vert_mask = vert_selection_from_edge(
|
|
src_edges, edge_mask, src_mesh.totvert, memory.local());
|
|
},
|
|
[&]() {
|
|
face_mask = face_selection_from_edge(
|
|
src_faces, src_corner_edges, span, memory.local());
|
|
});
|
|
break;
|
|
}
|
|
case ATTR_DOMAIN_FACE: {
|
|
const VArraySpan<bool> span(selection);
|
|
face_mask = IndexMask::from_bools(span, memory.local());
|
|
threading::parallel_invoke(
|
|
face_mask.size() > 1024,
|
|
[&]() {
|
|
vert_mask = vert_selection_from_face(
|
|
src_faces, face_mask, src_corner_verts, src_mesh.totvert, memory.local());
|
|
},
|
|
[&]() {
|
|
edge_mask = edge_selection_from_face(
|
|
src_faces, face_mask, src_corner_edges, src_mesh.totedge, memory.local());
|
|
});
|
|
break;
|
|
}
|
|
default:
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
|
|
if (vert_mask.is_empty()) {
|
|
return nullptr;
|
|
}
|
|
const bool same_verts = vert_mask.size() == src_mesh.totvert;
|
|
const bool same_edges = edge_mask.size() == src_mesh.totedge;
|
|
const bool same_faces = face_mask.size() == src_mesh.faces_num;
|
|
if (same_verts && same_edges && same_faces) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
Mesh *dst_mesh = create_mesh_no_attributes(
|
|
src_mesh, vert_mask.size(), edge_mask.size(), face_mask.size(), 0);
|
|
bke::MutableAttributeAccessor dst_attributes = dst_mesh->attributes_for_write();
|
|
dst_attributes.add<int2>(".edge_verts", ATTR_DOMAIN_EDGE, bke::AttributeInitConstruct());
|
|
MutableSpan<int2> dst_edges = dst_mesh->edges_for_write();
|
|
|
|
const OffsetIndices<int> dst_faces = offset_indices::gather_selected_offsets(
|
|
src_faces, face_mask, dst_mesh->face_offsets_for_write());
|
|
dst_mesh->totloop = dst_faces.total_size();
|
|
dst_attributes.add<int>(".corner_vert", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
|
|
dst_attributes.add<int>(".corner_edge", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
|
|
MutableSpan<int> dst_corner_verts = dst_mesh->corner_verts_for_write();
|
|
MutableSpan<int> dst_corner_edges = dst_mesh->corner_edges_for_write();
|
|
|
|
threading::parallel_invoke(
|
|
vert_mask.size() > 1024,
|
|
[&]() {
|
|
remap_verts(src_faces,
|
|
dst_faces,
|
|
src_mesh.totvert,
|
|
vert_mask,
|
|
edge_mask,
|
|
face_mask,
|
|
src_edges,
|
|
src_corner_verts,
|
|
dst_edges,
|
|
dst_corner_verts);
|
|
},
|
|
[&]() {
|
|
remap_edges(src_faces,
|
|
dst_faces,
|
|
src_edges.size(),
|
|
edge_mask,
|
|
face_mask,
|
|
src_corner_edges,
|
|
dst_corner_edges);
|
|
},
|
|
[&]() {
|
|
gather_vert_attributes(src_mesh, propagation_info, vert_mask, *dst_mesh);
|
|
bke::gather_attributes(src_attributes,
|
|
ATTR_DOMAIN_EDGE,
|
|
propagation_info,
|
|
{".edge_verts"},
|
|
edge_mask,
|
|
dst_attributes);
|
|
bke::gather_attributes(
|
|
src_attributes, ATTR_DOMAIN_FACE, propagation_info, {}, face_mask, dst_attributes);
|
|
bke::gather_attributes_group_to_group(src_attributes,
|
|
ATTR_DOMAIN_CORNER,
|
|
propagation_info,
|
|
{".corner_edge", ".corner_vert"},
|
|
src_faces,
|
|
dst_faces,
|
|
face_mask,
|
|
dst_attributes);
|
|
});
|
|
|
|
if (selection_domain == ATTR_DOMAIN_EDGE) {
|
|
copy_loose_vert_hint(src_mesh, *dst_mesh);
|
|
}
|
|
else if (selection_domain == ATTR_DOMAIN_FACE) {
|
|
copy_loose_vert_hint(src_mesh, *dst_mesh);
|
|
copy_loose_edge_hint(src_mesh, *dst_mesh);
|
|
}
|
|
copy_overlapping_hint(src_mesh, *dst_mesh);
|
|
|
|
return dst_mesh;
|
|
}
|
|
|
|
std::optional<Mesh *> mesh_copy_selection_keep_verts(
|
|
const Mesh &src_mesh,
|
|
const VArray<bool> &selection,
|
|
const eAttrDomain selection_domain,
|
|
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
|
{
|
|
const Span<int2> src_edges = src_mesh.edges();
|
|
const OffsetIndices src_faces = src_mesh.faces();
|
|
const Span<int> src_corner_verts = src_mesh.corner_verts();
|
|
const Span<int> src_corner_edges = src_mesh.corner_edges();
|
|
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
|
|
|
|
if (selection.is_empty()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
threading::EnumerableThreadSpecific<IndexMaskMemory> memory;
|
|
IndexMask edge_mask;
|
|
IndexMask face_mask;
|
|
switch (selection_domain) {
|
|
case ATTR_DOMAIN_POINT: {
|
|
const VArraySpan<bool> span(selection);
|
|
threading::parallel_invoke(
|
|
src_edges.size() > 1024,
|
|
[&]() { edge_mask = edge_selection_from_vert(src_edges, span, memory.local()); },
|
|
[&]() {
|
|
face_mask = face_selection_from_vert(
|
|
src_faces, src_corner_verts, span, memory.local());
|
|
});
|
|
break;
|
|
}
|
|
case ATTR_DOMAIN_EDGE: {
|
|
const VArraySpan<bool> span(selection);
|
|
threading::parallel_invoke(
|
|
src_edges.size() > 1024,
|
|
[&]() { edge_mask = IndexMask::from_bools(span, memory.local()); },
|
|
[&]() {
|
|
face_mask = face_selection_from_edge(
|
|
src_faces, src_corner_edges, span, memory.local());
|
|
});
|
|
break;
|
|
}
|
|
case ATTR_DOMAIN_FACE: {
|
|
const VArraySpan<bool> span(selection);
|
|
face_mask = IndexMask::from_bools(span, memory.local());
|
|
edge_mask = edge_selection_from_face(
|
|
src_faces, face_mask, src_corner_edges, src_edges.size(), memory.local());
|
|
break;
|
|
}
|
|
default:
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
|
|
const bool same_edges = edge_mask.size() == src_mesh.totedge;
|
|
const bool same_faces = face_mask.size() == src_mesh.faces_num;
|
|
if (same_edges && same_faces) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
Mesh *dst_mesh = create_mesh_no_attributes(
|
|
src_mesh, src_mesh.totvert, edge_mask.size(), face_mask.size(), 0);
|
|
bke::MutableAttributeAccessor dst_attributes = dst_mesh->attributes_for_write();
|
|
|
|
const OffsetIndices<int> dst_faces = offset_indices::gather_selected_offsets(
|
|
src_faces, face_mask, dst_mesh->face_offsets_for_write());
|
|
dst_mesh->totloop = dst_faces.total_size();
|
|
dst_attributes.add<int>(".corner_edge", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
|
|
MutableSpan<int> dst_corner_edges = dst_mesh->corner_edges_for_write();
|
|
|
|
threading::parallel_invoke(
|
|
[&]() {
|
|
remap_edges(src_faces,
|
|
dst_faces,
|
|
src_edges.size(),
|
|
edge_mask,
|
|
face_mask,
|
|
src_corner_edges,
|
|
dst_corner_edges);
|
|
},
|
|
[&]() {
|
|
bke::copy_attributes(
|
|
src_attributes, ATTR_DOMAIN_POINT, propagation_info, {}, dst_attributes);
|
|
bke::gather_attributes(
|
|
src_attributes, ATTR_DOMAIN_EDGE, propagation_info, {}, edge_mask, dst_attributes);
|
|
bke::gather_attributes(
|
|
src_attributes, ATTR_DOMAIN_FACE, propagation_info, {}, face_mask, dst_attributes);
|
|
bke::gather_attributes_group_to_group(src_attributes,
|
|
ATTR_DOMAIN_CORNER,
|
|
propagation_info,
|
|
{".corner_edge"},
|
|
src_faces,
|
|
dst_faces,
|
|
face_mask,
|
|
dst_attributes);
|
|
});
|
|
|
|
/* Positions are not changed by the operation, so the bounds are the same. */
|
|
dst_mesh->runtime->bounds_cache = src_mesh.runtime->bounds_cache;
|
|
if (selection_domain == ATTR_DOMAIN_FACE) {
|
|
copy_loose_edge_hint(src_mesh, *dst_mesh);
|
|
}
|
|
copy_overlapping_hint(src_mesh, *dst_mesh);
|
|
|
|
return dst_mesh;
|
|
}
|
|
|
|
std::optional<Mesh *> mesh_copy_selection_keep_edges(
|
|
const Mesh &src_mesh,
|
|
const VArray<bool> &selection,
|
|
const eAttrDomain selection_domain,
|
|
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
|
{
|
|
const OffsetIndices src_faces = src_mesh.faces();
|
|
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
|
|
|
|
if (selection.is_empty()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
IndexMaskMemory memory;
|
|
IndexMask face_mask;
|
|
switch (selection_domain) {
|
|
case ATTR_DOMAIN_POINT:
|
|
face_mask = face_selection_from_vert(
|
|
src_faces, src_mesh.corner_verts(), VArraySpan(selection), memory);
|
|
break;
|
|
case ATTR_DOMAIN_EDGE:
|
|
face_mask = face_selection_from_edge(
|
|
src_faces, src_mesh.corner_edges(), VArraySpan(selection), memory);
|
|
break;
|
|
case ATTR_DOMAIN_FACE:
|
|
face_mask = IndexMask::from_bools(selection, memory);
|
|
break;
|
|
default:
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
|
|
const bool same_faces = face_mask.size() == src_mesh.faces_num;
|
|
if (same_faces) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
Mesh *dst_mesh = create_mesh_no_attributes(
|
|
src_mesh, src_mesh.totvert, src_mesh.totedge, face_mask.size(), 0);
|
|
bke::MutableAttributeAccessor dst_attributes = dst_mesh->attributes_for_write();
|
|
|
|
const OffsetIndices<int> dst_faces = offset_indices::gather_selected_offsets(
|
|
src_faces, face_mask, dst_mesh->face_offsets_for_write());
|
|
dst_mesh->totloop = dst_faces.total_size();
|
|
dst_attributes.add<int>(".corner_vert", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
|
|
dst_attributes.add<int>(".corner_edge", ATTR_DOMAIN_CORNER, bke::AttributeInitConstruct());
|
|
|
|
bke::copy_attributes(src_attributes, ATTR_DOMAIN_POINT, propagation_info, {}, dst_attributes);
|
|
bke::copy_attributes(src_attributes, ATTR_DOMAIN_EDGE, propagation_info, {}, dst_attributes);
|
|
bke::gather_attributes(
|
|
src_attributes, ATTR_DOMAIN_FACE, propagation_info, {}, face_mask, dst_attributes);
|
|
bke::gather_attributes_group_to_group(src_attributes,
|
|
ATTR_DOMAIN_CORNER,
|
|
propagation_info,
|
|
{},
|
|
src_faces,
|
|
dst_faces,
|
|
face_mask,
|
|
dst_attributes);
|
|
|
|
/* Positions are not changed by the operation, so the bounds are the same. */
|
|
dst_mesh->runtime->bounds_cache = src_mesh.runtime->bounds_cache;
|
|
copy_loose_vert_hint(src_mesh, *dst_mesh);
|
|
copy_overlapping_hint(src_mesh, *dst_mesh);
|
|
return dst_mesh;
|
|
}
|
|
|
|
} // namespace blender::geometry
|