Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
572 lines
22 KiB
C++
572 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 "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();
|
|
}
|
|
}
|
|
|
|
/** 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 fn::Field<bool> &selection_field,
|
|
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();
|
|
|
|
const bke::MeshFieldContext context(src_mesh, selection_domain);
|
|
fn::FieldEvaluator evaluator(context, src_attributes.domain_size(selection_domain));
|
|
evaluator.add(selection_field);
|
|
evaluator.evaluate();
|
|
const VArray<bool> selection = evaluator.get_evaluated<bool>(0);
|
|
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);
|
|
}
|
|
|
|
return dst_mesh;
|
|
}
|
|
|
|
std::optional<Mesh *> mesh_copy_selection_keep_verts(
|
|
const Mesh &src_mesh,
|
|
const fn::Field<bool> &selection_field,
|
|
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();
|
|
|
|
const bke::MeshFieldContext context(src_mesh, selection_domain);
|
|
fn::FieldEvaluator evaluator(context, src_attributes.domain_size(selection_domain));
|
|
evaluator.add(selection_field);
|
|
evaluator.evaluate();
|
|
const VArray<bool> selection = evaluator.get_evaluated<bool>(0);
|
|
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;
|
|
copy_loose_vert_hint(src_mesh, *dst_mesh);
|
|
return dst_mesh;
|
|
}
|
|
|
|
std::optional<Mesh *> mesh_copy_selection_keep_edges(
|
|
const Mesh &src_mesh,
|
|
const fn::Field<bool> &selection_field,
|
|
const eAttrDomain selection_domain,
|
|
const bke::AnonymousAttributePropagationInfo &propagation_info)
|
|
{
|
|
const OffsetIndices src_faces = src_mesh.faces();
|
|
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
|
|
|
|
const bke::MeshFieldContext context(src_mesh, selection_domain);
|
|
fn::FieldEvaluator evaluator(context, src_attributes.domain_size(selection_domain));
|
|
evaluator.add(selection_field);
|
|
evaluator.evaluate();
|
|
const VArray<bool> selection = evaluator.get_evaluated<bool>(0);
|
|
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);
|
|
return dst_mesh;
|
|
}
|
|
|
|
} // namespace blender::geometry
|