diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 228c27cedf7..2d9159f823f 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -1,104 +1,105 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_listbase.h" #include "BLI_task.hh" +#include "atomic_ops.h #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "BKE_attribute_math.hh" #include "BKE_deform.h" #include "BKE_geometry_fields.hh" #include "BKE_geometry_set.hh" #include "BKE_lib_id.h" #include "BKE_mesh.h" #include "FN_multi_function_builder.hh" #include "attribute_access_intern.hh" extern "C" MDeformVert *BKE_object_defgroup_data_create(ID *id); /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation * \{ */ MeshComponent::MeshComponent() : GeometryComponent(GEO_COMPONENT_TYPE_MESH) { } MeshComponent::~MeshComponent() { this->clear(); } GeometryComponent *MeshComponent::copy() const { MeshComponent *new_component = new MeshComponent(); if (mesh_ != nullptr) { new_component->mesh_ = BKE_mesh_copy_for_eval(mesh_, false); new_component->ownership_ = GeometryOwnershipType::Owned; } return new_component; } void MeshComponent::clear() { BLI_assert(this->is_mutable()); if (mesh_ != nullptr) { if (ownership_ == GeometryOwnershipType::Owned) { BKE_id_free(nullptr, mesh_); } mesh_ = nullptr; } } bool MeshComponent::has_mesh() const { return mesh_ != nullptr; } void MeshComponent::replace(Mesh *mesh, GeometryOwnershipType ownership) { BLI_assert(this->is_mutable()); this->clear(); mesh_ = mesh; ownership_ = ownership; } Mesh *MeshComponent::release() { BLI_assert(this->is_mutable()); Mesh *mesh = mesh_; mesh_ = nullptr; return mesh; } const Mesh *MeshComponent::get_for_read() const { return mesh_; } Mesh *MeshComponent::get_for_write() { BLI_assert(this->is_mutable()); if (ownership_ == GeometryOwnershipType::ReadOnly) { mesh_ = BKE_mesh_copy_for_eval(mesh_, false); ownership_ = GeometryOwnershipType::Owned; } return mesh_; } bool MeshComponent::is_empty() const { return mesh_ == nullptr; } bool MeshComponent::owns_direct_data() const { return ownership_ == GeometryOwnershipType::Owned; } void MeshComponent::ensure_owns_direct_data() { @@ -116,692 +117,698 @@ void MeshComponent::ensure_owns_direct_data() * \{ */ namespace blender::bke { VArray mesh_normals_varray(const Mesh &mesh, const IndexMask mask, const eAttrDomain domain) { switch (domain) { case ATTR_DOMAIN_FACE: { return VArray::ForSpan( {(float3 *)BKE_mesh_poly_normals_ensure(&mesh), mesh.totpoly}); } case ATTR_DOMAIN_POINT: { return VArray::ForSpan( {(float3 *)BKE_mesh_vertex_normals_ensure(&mesh), mesh.totvert}); } case ATTR_DOMAIN_EDGE: { /* In this case, start with vertex normals and convert to the edge domain, since the * conversion from edges to vertices is very simple. Use "manual" domain interpolation * instead of the GeometryComponent API to avoid calculating unnecessary values and to * allow normalizing the result more simply. */ Span vert_normals{(float3 *)BKE_mesh_vertex_normals_ensure(&mesh), mesh.totvert}; const Span edges = mesh.edges(); Array edge_normals(mask.min_array_size()); for (const int i : mask) { const MEdge &edge = edges[i]; edge_normals[i] = math::normalize( math::interpolate(vert_normals[edge.v1], vert_normals[edge.v2], 0.5f)); } return VArray::ForContainer(std::move(edge_normals)); } case ATTR_DOMAIN_CORNER: { /* The normals on corners are just the mesh's face normals, so start with the face normal * array and copy the face normal for each of its corners. In this case using the mesh * component's generic domain interpolation is fine, the data will still be normalized, * since the face normal is just copied to every corner. */ return mesh.attributes().adapt_domain( VArray::ForSpan({(float3 *)BKE_mesh_poly_normals_ensure(&mesh), mesh.totpoly}), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); } default: return {}; } } } // namespace blender::bke /** \} */ /* -------------------------------------------------------------------- */ /** \name Attribute Access * \{ */ namespace blender::bke { template static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totvert); const Span loops = mesh.loops(); attribute_math::DefaultMixer mixer(r_values); for (const int loop_index : IndexRange(mesh.totloop)) { const T value = old_values[loop_index]; const MLoop &loop = loops[loop_index]; const int point_index = loop.v; mixer.mix_in(point_index, value); } mixer.finalize(); } /* A vertex is selected if all connected face corners were selected and it is not loose. */ template<> void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totvert); const Span loops = mesh.loops(); Array loose_verts(mesh.totvert, true); r_values.fill(true); for (const int loop_index : IndexRange(mesh.totloop)) { const MLoop &loop = loops[loop_index]; const int point_index = loop.v; loose_verts[point_index] = false; if (!old_values[loop_index]) { r_values[point_index] = false; } } /* Deselect loose vertices without corners that are still selected from the 'true' default. */ - for (const int vert_index : IndexRange(mesh.totvert)) { - if (loose_verts[vert_index]) { - r_values[vert_index] = false; + threading::parallel_for(IndexRange(mesh.totvert), 2048, [&](const IndexRange range) { + for (const int vert_index : range) { + if (loose_verts[vert_index]) { + r_values[vert_index] = false; + } } - } + }); } static GVArray adapt_mesh_domain_corner_to_point(const Mesh &mesh, const GVArray &varray) { GArray<> values(varray.type(), mesh.totvert); attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { /* We compute all interpolated values at once, because for this interpolation, one has to * iterate over all loops anyway. */ adapt_mesh_domain_corner_to_point_impl( mesh, varray.typed(), values.as_mutable_span().typed()); } }); return GVArray::ForGArray(std::move(values)); } /** * Each corner's value is simply a copy of the value at its vertex. */ static GVArray adapt_mesh_domain_point_to_corner(const Mesh &mesh, const GVArray &varray) { const Span loops = mesh.loops(); GVArray new_varray; attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); new_varray = VArray::ForFunc(mesh.totloop, [loops, varray = varray.typed()](const int64_t loop_index) { const int vertex_index = loops[loop_index].v; return varray[vertex_index]; }); }); return new_varray; } static GVArray adapt_mesh_domain_corner_to_face(const Mesh &mesh, const GVArray &varray) { const Span polys = mesh.polys(); GVArray new_varray; attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { if constexpr (std::is_same_v) { new_varray = VArray::ForFunc( polys.size(), [polys, varray = varray.typed()](const int face_index) { /* A face is selected if all of its corners were selected. */ const MPoly &poly = polys[face_index]; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { if (!varray[loop_index]) { return false; } } return true; }); } else { new_varray = VArray::ForFunc( polys.size(), [polys, varray = varray.typed()](const int face_index) { T return_value; attribute_math::DefaultMixer mixer({&return_value, 1}); const MPoly &poly = polys[face_index]; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const T value = varray[loop_index]; mixer.mix_in(0, value); } mixer.finalize(); return return_value; }); } } }); return new_varray; } template static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totedge); const Span polys = mesh.polys(); const Span loops = mesh.loops(); attribute_math::DefaultMixer mixer(r_values); for (const int poly_index : polys.index_range()) { const MPoly &poly = polys[poly_index]; /* For every edge, mix values from the two adjacent corners (the current and next corner). */ for (const int i : IndexRange(poly.totloop)) { const int next_i = (i + 1) % poly.totloop; const int loop_i = poly.loopstart + i; const int next_loop_i = poly.loopstart + next_i; const MLoop &loop = loops[loop_i]; const int edge_index = loop.e; mixer.mix_in(edge_index, old_values[loop_i]); mixer.mix_in(edge_index, old_values[next_loop_i]); } } mixer.finalize(); } /* An edge is selected if all corners on adjacent faces were selected. */ template<> void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totedge); const Span polys = mesh.polys(); const Span loops = mesh.loops(); /* It may be possible to rely on the #ME_LOOSEEDGE flag, but that seems error-prone. */ Array loose_edges(mesh.totedge, true); r_values.fill(true); for (const int poly_index : polys.index_range()) { const MPoly &poly = polys[poly_index]; for (const int i : IndexRange(poly.totloop)) { const int next_i = (i + 1) % poly.totloop; const int loop_i = poly.loopstart + i; const int next_loop_i = poly.loopstart + next_i; const MLoop &loop = loops[loop_i]; const int edge_index = loop.e; loose_edges[edge_index] = false; if (!old_values[loop_i] || !old_values[next_loop_i]) { r_values[edge_index] = false; } } } /* Deselect loose edges without corners that are still selected from the 'true' default. */ threading::parallel_for(IndexRange(mesh.totedge), 2048, [&](const IndexRange range) { for (const int edge_index : range) { if (loose_edges[edge_index]) { r_values[edge_index] = false; } } }); } static GVArray adapt_mesh_domain_corner_to_edge(const Mesh &mesh, const GVArray &varray) { GArray<> values(varray.type(), mesh.totedge); attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { adapt_mesh_domain_corner_to_edge_impl( mesh, varray.typed(), values.as_mutable_span().typed()); } }); return GVArray::ForGArray(std::move(values)); } template void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totvert); const Span polys = mesh.polys(); const Span loops = mesh.loops(); attribute_math::DefaultMixer mixer(r_values); for (const int poly_index : polys.index_range()) { const MPoly &poly = polys[poly_index]; const T value = old_values[poly_index]; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const MLoop &loop = loops[loop_index]; const int point_index = loop.v; mixer.mix_in(point_index, value); } } mixer.finalize(); } /* A vertex is selected if any of the connected faces were selected. */ template<> void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totvert); const Span polys = mesh.polys(); const Span loops = mesh.loops(); r_values.fill(false); - for (const int poly_index : polys.index_range()) { - const MPoly &poly = polys[poly_index]; - if (old_values[poly_index]) { - for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { - const MLoop &loop = loops[loop_index]; - const int vert_index = loop.v; - r_values[vert_index] = true; + threading::parallel_for(polys.index_range(), 2048, [&](const IndexRange range) { + for (const int poly_index : range) { + if (old_values[poly_index]) { + const MPoly &poly = polys[poly_index]; + for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + r_values[loop.v] = true; + } } } - } + }); } static GVArray adapt_mesh_domain_face_to_point(const Mesh &mesh, const GVArray &varray) { GArray<> values(varray.type(), mesh.totvert); attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { adapt_mesh_domain_face_to_point_impl( mesh, varray.typed(), values.as_mutable_span().typed()); } }); return GVArray::ForGArray(std::move(values)); } /* Each corner's value is simply a copy of the value at its face. */ template void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totloop); const Span polys = mesh.polys(); threading::parallel_for(polys.index_range(), 1024, [&](const IndexRange range) { for (const int poly_index : range) { const MPoly &poly = polys[poly_index]; MutableSpan poly_corner_values = r_values.slice(poly.loopstart, poly.totloop); poly_corner_values.fill(old_values[poly_index]); } }); } static GVArray adapt_mesh_domain_face_to_corner(const Mesh &mesh, const GVArray &varray) { GArray<> values(varray.type(), mesh.totloop); attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { adapt_mesh_domain_face_to_corner_impl( mesh, varray.typed(), values.as_mutable_span().typed()); } }); return GVArray::ForGArray(std::move(values)); } template void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totedge); const Span polys = mesh.polys(); const Span loops = mesh.loops(); attribute_math::DefaultMixer mixer(r_values); for (const int poly_index : polys.index_range()) { const MPoly &poly = polys[poly_index]; const T value = old_values[poly_index]; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const MLoop &loop = loops[loop_index]; mixer.mix_in(loop.e, value); } } mixer.finalize(); } /* An edge is selected if any connected face was selected. */ template<> void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totedge); const Span polys = mesh.polys(); const Span loops = mesh.loops(); r_values.fill(false); - for (const int poly_index : polys.index_range()) { - const MPoly &poly = polys[poly_index]; - if (old_values[poly_index]) { - for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { - const MLoop &loop = loops[loop_index]; - const int edge_index = loop.e; - r_values[edge_index] = true; + threading::parallel_for(polys.index_range(), 2048, [&](const IndexRange range) { + for (const int poly_index : range) { + if (old_values[poly_index]) { + const MPoly &poly = polys[poly_index]; + for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + r_values[loop.e] = true; + } } } - } + }); } static GVArray adapt_mesh_domain_face_to_edge(const Mesh &mesh, const GVArray &varray) { GArray<> values(varray.type(), mesh.totedge); attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { adapt_mesh_domain_face_to_edge_impl( mesh, varray.typed(), values.as_mutable_span().typed()); } }); return GVArray::ForGArray(std::move(values)); } static GVArray adapt_mesh_domain_point_to_face(const Mesh &mesh, const GVArray &varray) { const Span polys = mesh.polys(); const Span loops = mesh.loops(); GVArray new_varray; attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { if constexpr (std::is_same_v) { new_varray = VArray::ForFunc( mesh.totpoly, [loops, polys, varray = varray.typed()](const int face_index) { /* A face is selected if all of its vertices were selected. */ const MPoly &poly = polys[face_index]; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const MLoop &loop = loops[loop_index]; if (!varray[loop.v]) { return false; } } return true; }); } else { new_varray = VArray::ForFunc( mesh.totpoly, [loops, polys, varray = varray.typed()](const int face_index) { T return_value; attribute_math::DefaultMixer mixer({&return_value, 1}); const MPoly &poly = polys[face_index]; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const MLoop &loop = loops[loop_index]; const T value = varray[loop.v]; mixer.mix_in(0, value); } mixer.finalize(); return return_value; }); } } }); return new_varray; } static GVArray adapt_mesh_domain_point_to_edge(const Mesh &mesh, const GVArray &varray) { const Span edges = mesh.edges(); GVArray new_varray; attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { if constexpr (std::is_same_v) { /* An edge is selected if both of its vertices were selected. */ new_varray = VArray::ForFunc( edges.size(), [edges, varray = varray.typed()](const int edge_index) { const MEdge &edge = edges[edge_index]; return varray[edge.v1] && varray[edge.v2]; }); } else { new_varray = VArray::ForFunc( edges.size(), [edges, varray = varray.typed()](const int edge_index) { T return_value; attribute_math::DefaultMixer mixer({&return_value, 1}); const MEdge &edge = edges[edge_index]; mixer.mix_in(0, varray[edge.v1]); mixer.mix_in(0, varray[edge.v2]); mixer.finalize(); return return_value; }); } } }); return new_varray; } template void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totloop); const Span polys = mesh.polys(); const Span loops = mesh.loops(); attribute_math::DefaultMixer mixer(r_values); for (const int poly_index : polys.index_range()) { const MPoly &poly = polys[poly_index]; /* For every corner, mix the values from the adjacent edges on the face. */ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const int loop_index_prev = loop_index - 1 + (loop_index == poly.loopstart) * poly.totloop; const MLoop &loop = loops[loop_index]; const MLoop &loop_prev = loops[loop_index_prev]; mixer.mix_in(loop_index, old_values[loop.e]); mixer.mix_in(loop_index, old_values[loop_prev.e]); } } mixer.finalize(); } /* A corner is selected if its two adjacent edges were selected. */ template<> void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totloop); const Span polys = mesh.polys(); const Span loops = mesh.loops(); r_values.fill(false); - for (const int poly_index : polys.index_range()) { - const MPoly &poly = polys[poly_index]; - for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { - const int loop_index_prev = loop_index - 1 + (loop_index == poly.loopstart) * poly.totloop; - const MLoop &loop = loops[loop_index]; - const MLoop &loop_prev = loops[loop_index_prev]; - if (old_values[loop.e] && old_values[loop_prev.e]) { - r_values[loop_index] = true; + threading::parallel_for(polys.index_range(), 2048, [&](const IndexRange range) { + for (const int poly_index : range) { + const MPoly &poly = polys[poly_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const int loop_index_prev = loop_index - 1 + (loop_index == poly.loopstart) * poly.totloop; + const MLoop &loop = loops[loop_index]; + const MLoop &loop_prev = loops[loop_index_prev]; + if (old_values[loop.e] && old_values[loop_prev.e]) { + r_values[loop_index] = true; + } } } - } + }); } static GVArray adapt_mesh_domain_edge_to_corner(const Mesh &mesh, const GVArray &varray) { GArray<> values(varray.type(), mesh.totloop); attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { adapt_mesh_domain_edge_to_corner_impl( mesh, varray.typed(), values.as_mutable_span().typed()); } }); return GVArray::ForGArray(std::move(values)); } template static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totvert); const Span edges = mesh.edges(); attribute_math::DefaultMixer mixer(r_values); for (const int edge_index : IndexRange(mesh.totedge)) { const MEdge &edge = edges[edge_index]; const T value = old_values[edge_index]; mixer.mix_in(edge.v1, value); mixer.mix_in(edge.v2, value); } mixer.finalize(); } /* A vertex is selected if any connected edge was selected. */ template<> void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, const VArray &old_values, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totvert); const Span edges = mesh.edges(); r_values.fill(false); - for (const int edge_index : edges.index_range()) { - const MEdge &edge = edges[edge_index]; - if (old_values[edge_index]) { - r_values[edge.v1] = true; - r_values[edge.v2] = true; + threading::parallel_for(edges.index_range(), 2048, [&](const IndexRange range) { + for (const int edge_index : range) { + if (old_values[edge_index]) { + const MEdge &edge = edges[edge_index]; + r_values[edge.v1] = true; + r_values[edge.v2] = true; + } } - } + }); } static GVArray adapt_mesh_domain_edge_to_point(const Mesh &mesh, const GVArray &varray) { GArray<> values(varray.type(), mesh.totvert); attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { adapt_mesh_domain_edge_to_point_impl( mesh, varray.typed(), values.as_mutable_span().typed()); } }); return GVArray::ForGArray(std::move(values)); } static GVArray adapt_mesh_domain_edge_to_face(const Mesh &mesh, const GVArray &varray) { const Span polys = mesh.polys(); const Span loops = mesh.loops(); GVArray new_varray; attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { if constexpr (std::is_same_v) { /* A face is selected if all of its edges are selected. */ new_varray = VArray::ForFunc( polys.size(), [loops, polys, varray = varray.typed()](const int face_index) { const MPoly &poly = polys[face_index]; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const MLoop &loop = loops[loop_index]; if (!varray[loop.e]) { return false; } } return true; }); } else { new_varray = VArray::ForFunc( polys.size(), [loops, polys, varray = varray.typed()](const int face_index) { T return_value; attribute_math::DefaultMixer mixer({&return_value, 1}); const MPoly &poly = polys[face_index]; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const MLoop &loop = loops[loop_index]; const T value = varray[loop.e]; mixer.mix_in(0, value); } mixer.finalize(); return return_value; }); } } }); return new_varray; } } // namespace blender::bke static bool can_simple_adapt_for_single(const eAttrDomain from_domain, const eAttrDomain to_domain) { /* For some domain combinations, a single value will always map directly. For others, there may * be loose elements on the result domain that should have the default value rather than the * single value from the source. */ switch (from_domain) { case ATTR_DOMAIN_POINT: /* All other domains are always connected to points. */ return true; case ATTR_DOMAIN_EDGE: /* There may be loose vertices not connected to edges. */ return ELEM(to_domain, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); case ATTR_DOMAIN_FACE: /* There may be loose vertices or edges not connected to faces. */ return to_domain == ATTR_DOMAIN_CORNER; case ATTR_DOMAIN_CORNER: /* Only faces are always connected to corners. */ return to_domain == ATTR_DOMAIN_FACE; default: BLI_assert_unreachable(); return false; } } static blender::GVArray adapt_mesh_attribute_domain(const Mesh &mesh, const blender::GVArray &varray, const eAttrDomain from_domain, const eAttrDomain to_domain) { if (!varray) { return {}; } if (varray.size() == 0) { return {}; } if (from_domain == to_domain) { return varray; } if (varray.is_single()) { if (can_simple_adapt_for_single(from_domain, to_domain)) {