Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenkernel/intern/geometry_component_mesh.cc
| Show All 26 Lines | |||||
| #include "BKE_lib_id.h" | #include "BKE_lib_id.h" | ||||
| #include "BKE_mesh.h" | #include "BKE_mesh.h" | ||||
| #include "attribute_access_intern.hh" | #include "attribute_access_intern.hh" | ||||
| /* Can't include BKE_object_deform.h right now, due to an enum forward declaration. */ | /* Can't include BKE_object_deform.h right now, due to an enum forward declaration. */ | ||||
| extern "C" MDeformVert *BKE_object_defgroup_data_create(ID *id); | extern "C" MDeformVert *BKE_object_defgroup_data_create(ID *id); | ||||
| using blender::bke::ReadAttributePtr; | using blender::fn::GVArray; | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Geometry Component Implementation | /** \name Geometry Component Implementation | ||||
| * \{ */ | * \{ */ | ||||
| MeshComponent::MeshComponent() : GeometryComponent(GEO_COMPONENT_TYPE_MESH) | MeshComponent::MeshComponent() : GeometryComponent(GEO_COMPONENT_TYPE_MESH) | ||||
| { | { | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 108 Lines • ▼ Show 20 Lines | Mesh *MeshComponent::get_for_write() | ||||
| return mesh_; | return mesh_; | ||||
| } | } | ||||
| bool MeshComponent::is_empty() const | bool MeshComponent::is_empty() const | ||||
| { | { | ||||
| return mesh_ == nullptr; | return mesh_ == nullptr; | ||||
| } | } | ||||
| bool MeshComponent::owns_direct_data() const | |||||
| { | |||||
| return ownership_ == GeometryOwnershipType::Owned; | |||||
| } | |||||
| void MeshComponent::ensure_owns_direct_data() | |||||
| { | |||||
| BLI_assert(this->is_mutable()); | |||||
| if (ownership_ != GeometryOwnershipType::Owned) { | |||||
| mesh_ = BKE_mesh_copy_for_eval(mesh_, false); | |||||
| ownership_ = GeometryOwnershipType::Owned; | |||||
| } | |||||
| } | |||||
| /** \} */ | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Attribute Access | /** \name Attribute Access | ||||
| * \{ */ | * \{ */ | ||||
| int MeshComponent::attribute_domain_size(const AttributeDomain domain) const | int MeshComponent::attribute_domain_size(const AttributeDomain domain) const | ||||
| { | { | ||||
| Show All 14 Lines | int MeshComponent::attribute_domain_size(const AttributeDomain domain) const | ||||
| } | } | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| namespace blender::bke { | namespace blender::bke { | ||||
| template<typename T> | template<typename T> | ||||
| static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, | static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, | ||||
| const TypedReadAttribute<T> &attribute, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totvert); | BLI_assert(r_values.size() == mesh.totvert); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int loop_index : IndexRange(mesh.totloop)) { | for (const int loop_index : IndexRange(mesh.totloop)) { | ||||
| const T value = attribute[loop_index]; | const T value = old_values[loop_index]; | ||||
| const MLoop &loop = mesh.mloop[loop_index]; | const MLoop &loop = mesh.mloop[loop_index]; | ||||
| const int point_index = loop.v; | const int point_index = loop.v; | ||||
| mixer.mix_in(point_index, value); | mixer.mix_in(point_index, value); | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| /* We compute all interpolated values at once, because for this interpolation, one has to | /* We compute all interpolated values at once, because for this interpolation, one has to | ||||
| * iterate over all loops anyway. */ | * iterate over all loops anyway. */ | ||||
| Array<T> values(mesh.totvert); | Array<T> values(mesh.totvert); | ||||
| adapt_mesh_domain_corner_to_point_impl<T>(mesh, *attribute, values); | adapt_mesh_domain_corner_to_point_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| template<typename T> | template<typename T> | ||||
| static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh, | static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh, | ||||
| const TypedReadAttribute<T> &attribute, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totloop); | BLI_assert(r_values.size() == mesh.totloop); | ||||
| for (const int loop_index : IndexRange(mesh.totloop)) { | for (const int loop_index : IndexRange(mesh.totloop)) { | ||||
| const int vertex_index = mesh.mloop[loop_index].v; | const int vertex_index = mesh.mloop[loop_index].v; | ||||
| r_values[loop_index] = attribute[vertex_index]; | r_values[loop_index] = old_values[vertex_index]; | ||||
| } | } | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| /* It is not strictly necessary to compute the value for all corners here. Instead one could | /* It is not strictly necessary to compute the value for all corners here. Instead one could | ||||
| * lazily lookup the mesh topology when a specific index accessed. This can be more efficient | * lazily lookup the mesh topology when a specific index accessed. This can be more efficient | ||||
| * when an algorithm only accesses very few of the corner values. However, for the algorithms | * when an algorithm only accesses very few of the corner values. However, for the algorithms | ||||
| * we currently have, precomputing the array is fine. Also, it is easier to implement. */ | * we currently have, precomputing the array is fine. Also, it is easier to implement. */ | ||||
| Array<T> values(mesh.totloop); | Array<T> values(mesh.totloop); | ||||
| adapt_mesh_domain_point_to_corner_impl<T>(mesh, *attribute, values); | adapt_mesh_domain_point_to_corner_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_CORNER, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| /** | /** | ||||
| * \note Theoretically this interpolation does not need to compute all values at once. | * \note Theoretically this interpolation does not need to compute all values at once. | ||||
| * However, doing that makes the implementation simpler, and this can be optimized in the future if | * However, doing that makes the implementation simpler, and this can be optimized in the future if | ||||
| * only some values are required. | * only some values are required. | ||||
| */ | */ | ||||
| template<typename T> | template<typename T> | ||||
| static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, | static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, | ||||
| Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totpoly); | BLI_assert(r_values.size() == mesh.totpoly); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int poly_index : IndexRange(mesh.totpoly)) { | for (const int poly_index : IndexRange(mesh.totpoly)) { | ||||
| const MPoly &poly = mesh.mpoly[poly_index]; | const MPoly &poly = mesh.mpoly[poly_index]; | ||||
| for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | ||||
| const T value = old_values[loop_index]; | const T value = old_values[loop_index]; | ||||
| mixer.mix_in(poly_index, value); | mixer.mix_in(poly_index, value); | ||||
| } | } | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totpoly); | Array<T> values(mesh.totpoly); | ||||
| adapt_mesh_domain_corner_to_face_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_corner_to_face_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| template<typename T> | template<typename T> | ||||
| static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, | static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, | ||||
| Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totedge); | BLI_assert(r_values.size() == mesh.totedge); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int poly_index : IndexRange(mesh.totpoly)) { | for (const int poly_index : IndexRange(mesh.totpoly)) { | ||||
| const MPoly &poly = mesh.mpoly[poly_index]; | const MPoly &poly = mesh.mpoly[poly_index]; | ||||
| /* For every edge, mix values from the two adjacent corners (the current and next corner). */ | /* For every edge, mix values from the two adjacent corners (the current and next corner). */ | ||||
| for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | ||||
| const int loop_index_next = (loop_index + 1) % poly.totloop; | const int loop_index_next = (loop_index + 1) % poly.totloop; | ||||
| const MLoop &loop = mesh.mloop[loop_index]; | const MLoop &loop = mesh.mloop[loop_index]; | ||||
| const int edge_index = loop.e; | const int edge_index = loop.e; | ||||
| mixer.mix_in(edge_index, old_values[loop_index]); | mixer.mix_in(edge_index, old_values[loop_index]); | ||||
| mixer.mix_in(edge_index, old_values[loop_index_next]); | mixer.mix_in(edge_index, old_values[loop_index_next]); | ||||
| } | } | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totedge); | Array<T> values(mesh.totedge); | ||||
| adapt_mesh_domain_corner_to_edge_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_corner_to_edge_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| template<typename T> | template<typename T> | ||||
| void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, | void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, | ||||
| Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totvert); | BLI_assert(r_values.size() == mesh.totvert); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int poly_index : IndexRange(mesh.totpoly)) { | for (const int poly_index : IndexRange(mesh.totpoly)) { | ||||
| const MPoly &poly = mesh.mpoly[poly_index]; | const MPoly &poly = mesh.mpoly[poly_index]; | ||||
| const T value = old_values[poly_index]; | const T value = old_values[poly_index]; | ||||
| for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | ||||
| const MLoop &loop = mesh.mloop[loop_index]; | const MLoop &loop = mesh.mloop[loop_index]; | ||||
| const int point_index = loop.v; | const int point_index = loop.v; | ||||
| mixer.mix_in(point_index, value); | mixer.mix_in(point_index, value); | ||||
| } | } | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_face_to_point(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totvert); | Array<T> values(mesh.totvert); | ||||
| adapt_mesh_domain_face_to_point_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_face_to_point_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| template<typename T> | template<typename T> | ||||
| void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, | void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, | ||||
| const Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totloop); | BLI_assert(r_values.size() == mesh.totloop); | ||||
| for (const int poly_index : IndexRange(mesh.totpoly)) { | for (const int poly_index : IndexRange(mesh.totpoly)) { | ||||
| const MPoly &poly = mesh.mpoly[poly_index]; | const MPoly &poly = mesh.mpoly[poly_index]; | ||||
| MutableSpan<T> poly_corner_values = r_values.slice(poly.loopstart, poly.totloop); | MutableSpan<T> poly_corner_values = r_values.slice(poly.loopstart, poly.totloop); | ||||
| poly_corner_values.fill(old_values[poly_index]); | poly_corner_values.fill(old_values[poly_index]); | ||||
| } | } | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_face_to_corner(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_face_to_corner(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totloop); | Array<T> values(mesh.totloop); | ||||
| adapt_mesh_domain_face_to_corner_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_face_to_corner_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| template<typename T> | template<typename T> | ||||
| void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, | void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, | ||||
| const Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totedge); | BLI_assert(r_values.size() == mesh.totedge); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int poly_index : IndexRange(mesh.totpoly)) { | for (const int poly_index : IndexRange(mesh.totpoly)) { | ||||
| const MPoly &poly = mesh.mpoly[poly_index]; | const MPoly &poly = mesh.mpoly[poly_index]; | ||||
| const T value = old_values[poly_index]; | const T value = old_values[poly_index]; | ||||
| for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | ||||
| const MLoop &loop = mesh.mloop[loop_index]; | const MLoop &loop = mesh.mloop[loop_index]; | ||||
| mixer.mix_in(loop.e, value); | mixer.mix_in(loop.e, value); | ||||
| } | } | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totedge); | Array<T> values(mesh.totedge); | ||||
| adapt_mesh_domain_face_to_edge_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_face_to_edge_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| /** | /** | ||||
| * \note Theoretically this interpolation does not need to compute all values at once. | * \note Theoretically this interpolation does not need to compute all values at once. | ||||
| * However, doing that makes the implementation simpler, and this can be optimized in the future if | * However, doing that makes the implementation simpler, and this can be optimized in the future if | ||||
| * only some values are required. | * only some values are required. | ||||
| */ | */ | ||||
| template<typename T> | template<typename T> | ||||
| static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, | static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, | ||||
| const Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totpoly); | BLI_assert(r_values.size() == mesh.totpoly); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int poly_index : IndexRange(mesh.totpoly)) { | for (const int poly_index : IndexRange(mesh.totpoly)) { | ||||
| const MPoly &poly = mesh.mpoly[poly_index]; | const MPoly &poly = mesh.mpoly[poly_index]; | ||||
| for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | ||||
| MLoop &loop = mesh.mloop[loop_index]; | MLoop &loop = mesh.mloop[loop_index]; | ||||
| const int point_index = loop.v; | const int point_index = loop.v; | ||||
| mixer.mix_in(poly_index, old_values[point_index]); | mixer.mix_in(poly_index, old_values[point_index]); | ||||
| } | } | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_point_to_face(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_point_to_face(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totpoly); | Array<T> values(mesh.totpoly); | ||||
| adapt_mesh_domain_point_to_face_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_point_to_face_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| /** | /** | ||||
| * \note Theoretically this interpolation does not need to compute all values at once. | * \note Theoretically this interpolation does not need to compute all values at once. | ||||
| * However, doing that makes the implementation simpler, and this can be optimized in the future if | * However, doing that makes the implementation simpler, and this can be optimized in the future if | ||||
| * only some values are required. | * only some values are required. | ||||
| */ | */ | ||||
| template<typename T> | template<typename T> | ||||
| static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, | static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, | ||||
| const Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totedge); | BLI_assert(r_values.size() == mesh.totedge); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int edge_index : IndexRange(mesh.totedge)) { | for (const int edge_index : IndexRange(mesh.totedge)) { | ||||
| const MEdge &edge = mesh.medge[edge_index]; | const MEdge &edge = mesh.medge[edge_index]; | ||||
| mixer.mix_in(edge_index, old_values[edge.v1]); | mixer.mix_in(edge_index, old_values[edge.v1]); | ||||
| mixer.mix_in(edge_index, old_values[edge.v2]); | mixer.mix_in(edge_index, old_values[edge.v2]); | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totedge); | Array<T> values(mesh.totedge); | ||||
| adapt_mesh_domain_point_to_edge_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_point_to_edge_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| template<typename T> | template<typename T> | ||||
| void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, | void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, | ||||
| const Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totloop); | BLI_assert(r_values.size() == mesh.totloop); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int poly_index : IndexRange(mesh.totpoly)) { | for (const int poly_index : IndexRange(mesh.totpoly)) { | ||||
| const MPoly &poly = mesh.mpoly[poly_index]; | const MPoly &poly = mesh.mpoly[poly_index]; | ||||
| /* For every corner, mix the values from the adjacent edges on the face. */ | /* For every corner, mix the values from the adjacent edges on the face. */ | ||||
| for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | ||||
| const int loop_index_prev = (loop_index - 1) % poly.totloop; | const int loop_index_prev = (loop_index - 1) % poly.totloop; | ||||
| const MLoop &loop = mesh.mloop[loop_index]; | const MLoop &loop = mesh.mloop[loop_index]; | ||||
| const MLoop &loop_prev = mesh.mloop[loop_index_prev]; | const MLoop &loop_prev = mesh.mloop[loop_index_prev]; | ||||
| mixer.mix_in(loop_index, old_values[loop.e]); | mixer.mix_in(loop_index, old_values[loop.e]); | ||||
| mixer.mix_in(loop_index, old_values[loop_prev.e]); | mixer.mix_in(loop_index, old_values[loop_prev.e]); | ||||
| } | } | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totloop); | Array<T> values(mesh.totloop); | ||||
| adapt_mesh_domain_edge_to_corner_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_edge_to_corner_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| template<typename T> | template<typename T> | ||||
| static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, | static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, | ||||
| const Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totvert); | BLI_assert(r_values.size() == mesh.totvert); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int edge_index : IndexRange(mesh.totedge)) { | for (const int edge_index : IndexRange(mesh.totedge)) { | ||||
| const MEdge &edge = mesh.medge[edge_index]; | const MEdge &edge = mesh.medge[edge_index]; | ||||
| const T value = old_values[edge_index]; | const T value = old_values[edge_index]; | ||||
| mixer.mix_in(edge.v1, value); | mixer.mix_in(edge.v1, value); | ||||
| mixer.mix_in(edge.v2, value); | mixer.mix_in(edge.v2, value); | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totvert); | Array<T> values(mesh.totvert); | ||||
| adapt_mesh_domain_edge_to_point_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_edge_to_point_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| /** | /** | ||||
| * \note Theoretically this interpolation does not need to compute all values at once. | * \note Theoretically this interpolation does not need to compute all values at once. | ||||
| * However, doing that makes the implementation simpler, and this can be optimized in the future if | * However, doing that makes the implementation simpler, and this can be optimized in the future if | ||||
| * only some values are required. | * only some values are required. | ||||
| */ | */ | ||||
| template<typename T> | template<typename T> | ||||
| static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, | static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, | ||||
| const Span<T> old_values, | const VArray<T> &old_values, | ||||
| MutableSpan<T> r_values) | MutableSpan<T> r_values) | ||||
| { | { | ||||
| BLI_assert(r_values.size() == mesh.totpoly); | BLI_assert(r_values.size() == mesh.totpoly); | ||||
| attribute_math::DefaultMixer<T> mixer(r_values); | attribute_math::DefaultMixer<T> mixer(r_values); | ||||
| for (const int poly_index : IndexRange(mesh.totpoly)) { | for (const int poly_index : IndexRange(mesh.totpoly)) { | ||||
| const MPoly &poly = mesh.mpoly[poly_index]; | const MPoly &poly = mesh.mpoly[poly_index]; | ||||
| for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { | ||||
| const MLoop &loop = mesh.mloop[loop_index]; | const MLoop &loop = mesh.mloop[loop_index]; | ||||
| mixer.mix_in(poly_index, old_values[loop.e]); | mixer.mix_in(poly_index, old_values[loop.e]); | ||||
| } | } | ||||
| } | } | ||||
| mixer.finalize(); | mixer.finalize(); | ||||
| } | } | ||||
| static ReadAttributePtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, | static GVArrayPtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, GVArrayPtr varray) | ||||
| ReadAttributePtr attribute) | |||||
| { | { | ||||
| ReadAttributePtr new_attribute; | GVArrayPtr new_varray; | ||||
| const CustomDataType data_type = attribute->custom_data_type(); | const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); | ||||
| attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | attribute_math::convert_to_static_type(data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { | ||||
| Array<T> values(mesh.totpoly); | Array<T> values(mesh.totpoly); | ||||
| adapt_mesh_domain_edge_to_face_impl<T>(mesh, attribute->get_span<T>(), values); | adapt_mesh_domain_edge_to_face_impl<T>(mesh, varray->typed<T>(), values); | ||||
| new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, | new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); | ||||
| std::move(values)); | |||||
| } | } | ||||
| }); | }); | ||||
| return new_attribute; | return new_varray; | ||||
| } | } | ||||
| } // namespace blender::bke | } // namespace blender::bke | ||||
| ReadAttributePtr MeshComponent::attribute_try_adapt_domain(ReadAttributePtr attribute, | blender::fn::GVArrayPtr MeshComponent::attribute_try_adapt_domain( | ||||
| const AttributeDomain new_domain) const | blender::fn::GVArrayPtr varray, | ||||
| const AttributeDomain from_domain, | |||||
| const AttributeDomain to_domain) const | |||||
| { | { | ||||
| if (!attribute) { | if (!varray) { | ||||
| return {}; | return {}; | ||||
| } | } | ||||
| if (attribute->size() == 0) { | if (varray->size() == 0) { | ||||
| return {}; | return {}; | ||||
| } | } | ||||
| const AttributeDomain old_domain = attribute->domain(); | if (from_domain == to_domain) { | ||||
| if (old_domain == new_domain) { | return varray; | ||||
| return attribute; | |||||
| } | } | ||||
| switch (old_domain) { | switch (from_domain) { | ||||
| case ATTR_DOMAIN_CORNER: { | case ATTR_DOMAIN_CORNER: { | ||||
| switch (new_domain) { | switch (to_domain) { | ||||
| case ATTR_DOMAIN_POINT: | case ATTR_DOMAIN_POINT: | ||||
| return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, std::move(varray)); | ||||
| case ATTR_DOMAIN_FACE: | case ATTR_DOMAIN_FACE: | ||||
| return blender::bke::adapt_mesh_domain_corner_to_face(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_corner_to_face(*mesh_, std::move(varray)); | ||||
| case ATTR_DOMAIN_EDGE: | case ATTR_DOMAIN_EDGE: | ||||
| return blender::bke::adapt_mesh_domain_corner_to_edge(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_corner_to_edge(*mesh_, std::move(varray)); | ||||
| default: | default: | ||||
| break; | break; | ||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| case ATTR_DOMAIN_POINT: { | case ATTR_DOMAIN_POINT: { | ||||
| switch (new_domain) { | switch (to_domain) { | ||||
| case ATTR_DOMAIN_CORNER: | case ATTR_DOMAIN_CORNER: | ||||
| return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, std::move(varray)); | ||||
| case ATTR_DOMAIN_FACE: | case ATTR_DOMAIN_FACE: | ||||
| return blender::bke::adapt_mesh_domain_point_to_face(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_point_to_face(*mesh_, std::move(varray)); | ||||
| case ATTR_DOMAIN_EDGE: | case ATTR_DOMAIN_EDGE: | ||||
| return blender::bke::adapt_mesh_domain_point_to_edge(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_point_to_edge(*mesh_, std::move(varray)); | ||||
| default: | default: | ||||
| break; | break; | ||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| case ATTR_DOMAIN_FACE: { | case ATTR_DOMAIN_FACE: { | ||||
| switch (new_domain) { | switch (to_domain) { | ||||
| case ATTR_DOMAIN_POINT: | case ATTR_DOMAIN_POINT: | ||||
| return blender::bke::adapt_mesh_domain_face_to_point(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_face_to_point(*mesh_, std::move(varray)); | ||||
| case ATTR_DOMAIN_CORNER: | case ATTR_DOMAIN_CORNER: | ||||
| return blender::bke::adapt_mesh_domain_face_to_corner(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_face_to_corner(*mesh_, std::move(varray)); | ||||
| case ATTR_DOMAIN_EDGE: | case ATTR_DOMAIN_EDGE: | ||||
| return blender::bke::adapt_mesh_domain_face_to_edge(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_face_to_edge(*mesh_, std::move(varray)); | ||||
| default: | default: | ||||
| break; | break; | ||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| case ATTR_DOMAIN_EDGE: { | case ATTR_DOMAIN_EDGE: { | ||||
| switch (new_domain) { | switch (to_domain) { | ||||
| case ATTR_DOMAIN_CORNER: | case ATTR_DOMAIN_CORNER: | ||||
| return blender::bke::adapt_mesh_domain_edge_to_corner(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_edge_to_corner(*mesh_, std::move(varray)); | ||||
| case ATTR_DOMAIN_POINT: | case ATTR_DOMAIN_POINT: | ||||
| return blender::bke::adapt_mesh_domain_edge_to_point(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_edge_to_point(*mesh_, std::move(varray)); | ||||
| case ATTR_DOMAIN_FACE: | case ATTR_DOMAIN_FACE: | ||||
| return blender::bke::adapt_mesh_domain_edge_to_face(*mesh_, std::move(attribute)); | return blender::bke::adapt_mesh_domain_edge_to_face(*mesh_, std::move(varray)); | ||||
| default: | default: | ||||
| break; | break; | ||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| default: | default: | ||||
| break; | break; | ||||
| } | } | ||||
| Show All 12 Lines | |||||
| { | { | ||||
| BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); | BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); | ||||
| const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); | const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); | ||||
| return mesh_component.get_for_read(); | return mesh_component.get_for_read(); | ||||
| } | } | ||||
| namespace blender::bke { | namespace blender::bke { | ||||
| template<typename StructT, | template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &)> | ||||
| typename ElemT, | static GVArrayPtr make_derived_read_attribute(const void *data, const int domain_size) | ||||
| ElemT (*GetFunc)(const StructT &), | |||||
| AttributeDomain Domain> | |||||
| static ReadAttributePtr make_derived_read_attribute(const void *data, const int domain_size) | |||||
| { | { | ||||
| return std::make_unique<DerivedArrayReadAttribute<StructT, ElemT, GetFunc>>( | return std::make_unique<fn::GVArray_For_DerivedSpan<StructT, ElemT, GetFunc>>( | ||||
| Domain, Span<StructT>((const StructT *)data, domain_size)); | Span<StructT>((const StructT *)data, domain_size)); | ||||
| } | } | ||||
| template<typename StructT, | template<typename StructT, | ||||
| typename ElemT, | typename ElemT, | ||||
| ElemT (*GetFunc)(const StructT &), | ElemT (*GetFunc)(const StructT &), | ||||
| void (*SetFunc)(StructT &, const ElemT &), | void (*SetFunc)(StructT &, ElemT)> | ||||
| AttributeDomain Domain> | static GVMutableArrayPtr make_derived_write_attribute(void *data, const int domain_size) | ||||
| static WriteAttributePtr make_derived_write_attribute(void *data, const int domain_size) | |||||
| { | { | ||||
| return std::make_unique<DerivedArrayWriteAttribute<StructT, ElemT, GetFunc, SetFunc>>( | return std::make_unique<fn::GVMutableArray_For_DerivedSpan<StructT, ElemT, GetFunc, SetFunc>>( | ||||
| Domain, MutableSpan<StructT>((StructT *)data, domain_size)); | MutableSpan<StructT>((StructT *)data, domain_size)); | ||||
| } | } | ||||
| static float3 get_vertex_position(const MVert &vert) | static float3 get_vertex_position(const MVert &vert) | ||||
| { | { | ||||
| return float3(vert.co); | return float3(vert.co); | ||||
| } | } | ||||
| static void set_vertex_position(MVert &vert, const float3 &position) | static void set_vertex_position(MVert &vert, float3 position) | ||||
| { | { | ||||
| copy_v3_v3(vert.co, position); | copy_v3_v3(vert.co, position); | ||||
| } | } | ||||
| static void tag_normals_dirty_when_writing_position(GeometryComponent &component) | static void tag_normals_dirty_when_writing_position(GeometryComponent &component) | ||||
| { | { | ||||
| Mesh *mesh = get_mesh_from_component_for_write(component); | Mesh *mesh = get_mesh_from_component_for_write(component); | ||||
| if (mesh != nullptr) { | if (mesh != nullptr) { | ||||
| mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; | mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; | ||||
| } | } | ||||
| } | } | ||||
| static int get_material_index(const MPoly &mpoly) | static int get_material_index(const MPoly &mpoly) | ||||
| { | { | ||||
| return static_cast<int>(mpoly.mat_nr); | return static_cast<int>(mpoly.mat_nr); | ||||
| } | } | ||||
| static void set_material_index(MPoly &mpoly, const int &index) | static void set_material_index(MPoly &mpoly, int index) | ||||
| { | { | ||||
| mpoly.mat_nr = static_cast<short>(std::clamp(index, 0, SHRT_MAX)); | mpoly.mat_nr = static_cast<short>(std::clamp(index, 0, SHRT_MAX)); | ||||
| } | } | ||||
| static bool get_shade_smooth(const MPoly &mpoly) | static bool get_shade_smooth(const MPoly &mpoly) | ||||
| { | { | ||||
| return mpoly.flag & ME_SMOOTH; | return mpoly.flag & ME_SMOOTH; | ||||
| } | } | ||||
| static void set_shade_smooth(MPoly &mpoly, const bool &value) | static void set_shade_smooth(MPoly &mpoly, bool value) | ||||
| { | { | ||||
| SET_FLAG_FROM_TEST(mpoly.flag, value, ME_SMOOTH); | SET_FLAG_FROM_TEST(mpoly.flag, value, ME_SMOOTH); | ||||
| } | } | ||||
| static float2 get_loop_uv(const MLoopUV &uv) | static float2 get_loop_uv(const MLoopUV &uv) | ||||
| { | { | ||||
| return float2(uv.uv); | return float2(uv.uv); | ||||
| } | } | ||||
| static void set_loop_uv(MLoopUV &uv, const float2 &co) | static void set_loop_uv(MLoopUV &uv, float2 co) | ||||
| { | { | ||||
| copy_v2_v2(uv.uv, co); | copy_v2_v2(uv.uv, co); | ||||
| } | } | ||||
| static Color4f get_loop_color(const MLoopCol &col) | static Color4f get_loop_color(const MLoopCol &col) | ||||
| { | { | ||||
| Color4f value; | Color4f srgb_color; | ||||
| rgba_uchar_to_float(value, &col.r); | rgba_uchar_to_float(srgb_color, &col.r); | ||||
| return value; | Color4f linear_color; | ||||
| srgb_to_linearrgb_v4(linear_color, srgb_color); | |||||
| return linear_color; | |||||
| } | } | ||||
| static void set_loop_color(MLoopCol &col, const Color4f &value) | static void set_loop_color(MLoopCol &col, Color4f linear_color) | ||||
| { | { | ||||
| rgba_float_to_uchar(&col.r, value); | linearrgb_to_srgb_uchar4(&col.r, linear_color); | ||||
| } | } | ||||
| static float get_crease(const MEdge &edge) | static float get_crease(const MEdge &edge) | ||||
| { | { | ||||
| return edge.crease / 255.0f; | return edge.crease / 255.0f; | ||||
| } | } | ||||
| static void set_crease(MEdge &edge, const float &value) | static void set_crease(MEdge &edge, float value) | ||||
| { | { | ||||
| edge.crease = round_fl_to_uchar_clamp(value * 255.0f); | edge.crease = round_fl_to_uchar_clamp(value * 255.0f); | ||||
| } | } | ||||
| class VertexWeightWriteAttribute final : public WriteAttribute { | class VMutableArray_For_VertexWeights final : public VMutableArray<float> { | ||||
| private: | private: | ||||
| MDeformVert *dverts_; | MDeformVert *dverts_; | ||||
| const int dvert_index_; | const int dvert_index_; | ||||
| public: | public: | ||||
| VertexWeightWriteAttribute(MDeformVert *dverts, const int totvert, const int dvert_index) | VMutableArray_For_VertexWeights(MDeformVert *dverts, const int totvert, const int dvert_index) | ||||
| : WriteAttribute(ATTR_DOMAIN_POINT, CPPType::get<float>(), totvert), | : VMutableArray<float>(totvert), dverts_(dverts), dvert_index_(dvert_index) | ||||
| dverts_(dverts), | |||||
| dvert_index_(dvert_index) | |||||
| { | { | ||||
| } | } | ||||
| void get_internal(const int64_t index, void *r_value) const override | float get_impl(const int64_t index) const override | ||||
| { | { | ||||
| get_internal(dverts_, dvert_index_, index, r_value); | return get_internal(dverts_, dvert_index_, index); | ||||
| } | } | ||||
| void set_internal(const int64_t index, const void *value) override | void set_impl(const int64_t index, const float value) override | ||||
| { | { | ||||
| MDeformWeight *weight = BKE_defvert_ensure_index(&dverts_[index], dvert_index_); | MDeformWeight *weight = BKE_defvert_ensure_index(&dverts_[index], dvert_index_); | ||||
| weight->weight = *reinterpret_cast<const float *>(value); | weight->weight = value; | ||||
| } | } | ||||
| static void get_internal(const MDeformVert *dverts, | static float get_internal(const MDeformVert *dverts, const int dvert_index, const int64_t index) | ||||
| const int dvert_index, | |||||
| const int64_t index, | |||||
| void *r_value) | |||||
| { | { | ||||
| if (dverts == nullptr) { | if (dverts == nullptr) { | ||||
| *(float *)r_value = 0.0f; | return 0.0f; | ||||
| return; | |||||
| } | } | ||||
| const MDeformVert &dvert = dverts[index]; | const MDeformVert &dvert = dverts[index]; | ||||
| for (const MDeformWeight &weight : Span(dvert.dw, dvert.totweight)) { | for (const MDeformWeight &weight : Span(dvert.dw, dvert.totweight)) { | ||||
| if (weight.def_nr == dvert_index) { | if (weight.def_nr == dvert_index) { | ||||
| *(float *)r_value = weight.weight; | return weight.weight; | ||||
| return; | |||||
| } | } | ||||
| } | } | ||||
| *(float *)r_value = 0.0f; | return 0.0f; | ||||
| } | } | ||||
| }; | }; | ||||
| class VertexWeightReadAttribute final : public ReadAttribute { | class VArray_For_VertexWeights final : public VArray<float> { | ||||
| private: | private: | ||||
| const MDeformVert *dverts_; | const MDeformVert *dverts_; | ||||
| const int dvert_index_; | const int dvert_index_; | ||||
| public: | public: | ||||
| VertexWeightReadAttribute(const MDeformVert *dverts, const int totvert, const int dvert_index) | VArray_For_VertexWeights(const MDeformVert *dverts, const int totvert, const int dvert_index) | ||||
| : ReadAttribute(ATTR_DOMAIN_POINT, CPPType::get<float>(), totvert), | : VArray<float>(totvert), dverts_(dverts), dvert_index_(dvert_index) | ||||
| dverts_(dverts), | |||||
| dvert_index_(dvert_index) | |||||
| { | { | ||||
| } | } | ||||
| void get_internal(const int64_t index, void *r_value) const override | float get_impl(const int64_t index) const override | ||||
| { | { | ||||
| VertexWeightWriteAttribute::get_internal(dverts_, dvert_index_, index, r_value); | return VMutableArray_For_VertexWeights::get_internal(dverts_, dvert_index_, index); | ||||
| } | } | ||||
| }; | }; | ||||
| /** | /** | ||||
| * This provider makes vertex groups available as float attributes. | * This provider makes vertex groups available as float attributes. | ||||
| */ | */ | ||||
| class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { | class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { | ||||
| public: | public: | ||||
| ReadAttributePtr try_get_for_read(const GeometryComponent &component, | ReadAttributeLookup try_get_for_read(const GeometryComponent &component, | ||||
| const StringRef attribute_name) const final | const StringRef attribute_name) const final | ||||
| { | { | ||||
| BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); | BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); | ||||
| const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); | const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); | ||||
| const Mesh *mesh = mesh_component.get_for_read(); | const Mesh *mesh = mesh_component.get_for_read(); | ||||
| const int vertex_group_index = mesh_component.vertex_group_names().lookup_default_as( | const int vertex_group_index = mesh_component.vertex_group_names().lookup_default_as( | ||||
| attribute_name, -1); | attribute_name, -1); | ||||
| if (vertex_group_index < 0) { | if (vertex_group_index < 0) { | ||||
| return {}; | return {}; | ||||
| } | } | ||||
| if (mesh == nullptr || mesh->dvert == nullptr) { | if (mesh == nullptr || mesh->dvert == nullptr) { | ||||
| static const float default_value = 0.0f; | static const float default_value = 0.0f; | ||||
| return std::make_unique<ConstantReadAttribute>( | return {std::make_unique<fn::GVArray_For_SingleValueRef>( | ||||
| ATTR_DOMAIN_POINT, mesh->totvert, CPPType::get<float>(), &default_value); | CPPType::get<float>(), mesh->totvert, &default_value), | ||||
| ATTR_DOMAIN_POINT}; | |||||
| } | } | ||||
| return std::make_unique<VertexWeightReadAttribute>( | return {std::make_unique<fn::GVArray_For_EmbeddedVArray<float, VArray_For_VertexWeights>>( | ||||
| mesh->dvert, mesh->totvert, vertex_group_index); | mesh->totvert, mesh->dvert, mesh->totvert, vertex_group_index), | ||||
| ATTR_DOMAIN_POINT}; | |||||
| } | } | ||||
| WriteAttributePtr try_get_for_write(GeometryComponent &component, | WriteAttributeLookup try_get_for_write(GeometryComponent &component, | ||||
| const StringRef attribute_name) const final | const StringRef attribute_name) const final | ||||
| { | { | ||||
| BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); | BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); | ||||
| MeshComponent &mesh_component = static_cast<MeshComponent &>(component); | MeshComponent &mesh_component = static_cast<MeshComponent &>(component); | ||||
| Mesh *mesh = mesh_component.get_for_write(); | Mesh *mesh = mesh_component.get_for_write(); | ||||
| if (mesh == nullptr) { | if (mesh == nullptr) { | ||||
| return {}; | return {}; | ||||
| } | } | ||||
| const int vertex_group_index = mesh_component.vertex_group_names().lookup_default_as( | const int vertex_group_index = mesh_component.vertex_group_names().lookup_default_as( | ||||
| attribute_name, -1); | attribute_name, -1); | ||||
| if (vertex_group_index < 0) { | if (vertex_group_index < 0) { | ||||
| return {}; | return {}; | ||||
| } | } | ||||
| if (mesh->dvert == nullptr) { | if (mesh->dvert == nullptr) { | ||||
| BKE_object_defgroup_data_create(&mesh->id); | BKE_object_defgroup_data_create(&mesh->id); | ||||
| } | } | ||||
| else { | else { | ||||
| /* Copy the data layer if it is shared with some other mesh. */ | /* Copy the data layer if it is shared with some other mesh. */ | ||||
| mesh->dvert = (MDeformVert *)CustomData_duplicate_referenced_layer( | mesh->dvert = (MDeformVert *)CustomData_duplicate_referenced_layer( | ||||
| &mesh->vdata, CD_MDEFORMVERT, mesh->totvert); | &mesh->vdata, CD_MDEFORMVERT, mesh->totvert); | ||||
| } | } | ||||
| return std::make_unique<blender::bke::VertexWeightWriteAttribute>( | return { | ||||
| mesh->dvert, mesh->totvert, vertex_group_index); | std::make_unique< | ||||
| fn::GVMutableArray_For_EmbeddedVMutableArray<float, VMutableArray_For_VertexWeights>>( | |||||
| mesh->totvert, mesh->dvert, mesh->totvert, vertex_group_index), | |||||
| ATTR_DOMAIN_POINT}; | |||||
| } | } | ||||
| bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final | bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final | ||||
| { | { | ||||
| BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); | BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); | ||||
| MeshComponent &mesh_component = static_cast<MeshComponent &>(component); | MeshComponent &mesh_component = static_cast<MeshComponent &>(component); | ||||
| const int vertex_group_index = mesh_component.vertex_group_names().pop_default_as( | const int vertex_group_index = mesh_component.vertex_group_names().pop_default_as( | ||||
| ▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | |||||
| class NormalAttributeProvider final : public BuiltinAttributeProvider { | class NormalAttributeProvider final : public BuiltinAttributeProvider { | ||||
| public: | public: | ||||
| NormalAttributeProvider() | NormalAttributeProvider() | ||||
| : BuiltinAttributeProvider( | : BuiltinAttributeProvider( | ||||
| "normal", ATTR_DOMAIN_FACE, CD_PROP_FLOAT3, NonCreatable, Readonly, NonDeletable) | "normal", ATTR_DOMAIN_FACE, CD_PROP_FLOAT3, NonCreatable, Readonly, NonDeletable) | ||||
| { | { | ||||
| } | } | ||||
| ReadAttributePtr try_get_for_read(const GeometryComponent &component) const final | GVArrayPtr try_get_for_read(const GeometryComponent &component) const final | ||||
| { | { | ||||
| const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); | const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); | ||||
| const Mesh *mesh = mesh_component.get_for_read(); | const Mesh *mesh = mesh_component.get_for_read(); | ||||
| if (mesh == nullptr) { | if (mesh == nullptr) { | ||||
| return {}; | return {}; | ||||
| } | } | ||||
| /* Use existing normals if possible. */ | /* Use existing normals if possible. */ | ||||
| if (!(mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL) && | if (!(mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL) && | ||||
| CustomData_has_layer(&mesh->pdata, CD_NORMAL)) { | CustomData_has_layer(&mesh->pdata, CD_NORMAL)) { | ||||
| const void *data = CustomData_get_layer(&mesh->pdata, CD_NORMAL); | const void *data = CustomData_get_layer(&mesh->pdata, CD_NORMAL); | ||||
| return std::make_unique<ArrayReadAttribute<float3>>( | return std::make_unique<fn::GVArray_For_Span<float3>>( | ||||
| ATTR_DOMAIN_FACE, Span<float3>((const float3 *)data, mesh->totpoly)); | Span<float3>((const float3 *)data, mesh->totpoly)); | ||||
| } | } | ||||
| Array<float3> normals(mesh->totpoly); | Array<float3> normals(mesh->totpoly); | ||||
| for (const int i : IndexRange(mesh->totpoly)) { | for (const int i : IndexRange(mesh->totpoly)) { | ||||
| const MPoly *poly = &mesh->mpoly[i]; | const MPoly *poly = &mesh->mpoly[i]; | ||||
| BKE_mesh_calc_poly_normal(poly, &mesh->mloop[poly->loopstart], mesh->mvert, normals[i]); | BKE_mesh_calc_poly_normal(poly, &mesh->mloop[poly->loopstart], mesh->mvert, normals[i]); | ||||
| } | } | ||||
| return std::make_unique<OwnedArrayReadAttribute<float3>>(ATTR_DOMAIN_FACE, std::move(normals)); | return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); | ||||
| } | } | ||||
| WriteAttributePtr try_get_for_write(GeometryComponent &UNUSED(component)) const final | GVMutableArrayPtr try_get_for_write(GeometryComponent &UNUSED(component)) const final | ||||
| { | { | ||||
| return {}; | return {}; | ||||
| } | } | ||||
| bool try_delete(GeometryComponent &UNUSED(component)) const final | bool try_delete(GeometryComponent &UNUSED(component)) const final | ||||
| { | { | ||||
| return false; | return false; | ||||
| } | } | ||||
| bool try_create(GeometryComponent &UNUSED(component)) const final | bool try_create(GeometryComponent &UNUSED(component), | ||||
| const AttributeInit &UNUSED(initializer)) const final | |||||
| { | { | ||||
| return false; | return false; | ||||
| } | } | ||||
| bool exists(const GeometryComponent &component) const final | bool exists(const GeometryComponent &component) const final | ||||
| { | { | ||||
| return component.attribute_domain_size(ATTR_DOMAIN_FACE) != 0; | return component.attribute_domain_size(ATTR_DOMAIN_FACE) != 0; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | static BuiltinCustomDataLayerProvider position( | ||||
| "position", | "position", | ||||
| ATTR_DOMAIN_POINT, | ATTR_DOMAIN_POINT, | ||||
| CD_PROP_FLOAT3, | CD_PROP_FLOAT3, | ||||
| CD_MVERT, | CD_MVERT, | ||||
| BuiltinAttributeProvider::NonCreatable, | BuiltinAttributeProvider::NonCreatable, | ||||
| BuiltinAttributeProvider::Writable, | BuiltinAttributeProvider::Writable, | ||||
| BuiltinAttributeProvider::NonDeletable, | BuiltinAttributeProvider::NonDeletable, | ||||
| point_access, | point_access, | ||||
| make_derived_read_attribute<MVert, float3, get_vertex_position, ATTR_DOMAIN_POINT>, | make_derived_read_attribute<MVert, float3, get_vertex_position>, | ||||
| make_derived_write_attribute<MVert, | make_derived_write_attribute<MVert, float3, get_vertex_position, set_vertex_position>, | ||||
| float3, | |||||
| get_vertex_position, | |||||
| set_vertex_position, | |||||
| ATTR_DOMAIN_POINT>, | |||||
| tag_normals_dirty_when_writing_position); | tag_normals_dirty_when_writing_position); | ||||
| static NormalAttributeProvider normal; | static NormalAttributeProvider normal; | ||||
| static BuiltinCustomDataLayerProvider material_index( | static BuiltinCustomDataLayerProvider material_index( | ||||
| "material_index", | "material_index", | ||||
| ATTR_DOMAIN_FACE, | ATTR_DOMAIN_FACE, | ||||
| CD_PROP_INT32, | CD_PROP_INT32, | ||||
| CD_MPOLY, | CD_MPOLY, | ||||
| BuiltinAttributeProvider::NonCreatable, | BuiltinAttributeProvider::NonCreatable, | ||||
| BuiltinAttributeProvider::Writable, | BuiltinAttributeProvider::Writable, | ||||
| BuiltinAttributeProvider::NonDeletable, | BuiltinAttributeProvider::NonDeletable, | ||||
| face_access, | face_access, | ||||
| make_derived_read_attribute<MPoly, int, get_material_index, ATTR_DOMAIN_FACE>, | make_derived_read_attribute<MPoly, int, get_material_index>, | ||||
| make_derived_write_attribute<MPoly, | make_derived_write_attribute<MPoly, int, get_material_index, set_material_index>, | ||||
| int, | |||||
| get_material_index, | |||||
| set_material_index, | |||||
| ATTR_DOMAIN_FACE>, | |||||
| nullptr); | nullptr); | ||||
| static BuiltinCustomDataLayerProvider shade_smooth( | static BuiltinCustomDataLayerProvider shade_smooth( | ||||
| "shade_smooth", | "shade_smooth", | ||||
| ATTR_DOMAIN_FACE, | ATTR_DOMAIN_FACE, | ||||
| CD_PROP_BOOL, | CD_PROP_BOOL, | ||||
| CD_MPOLY, | CD_MPOLY, | ||||
| BuiltinAttributeProvider::NonCreatable, | BuiltinAttributeProvider::NonCreatable, | ||||
| BuiltinAttributeProvider::Writable, | BuiltinAttributeProvider::Writable, | ||||
| BuiltinAttributeProvider::NonDeletable, | BuiltinAttributeProvider::NonDeletable, | ||||
| face_access, | face_access, | ||||
| make_derived_read_attribute<MPoly, bool, get_shade_smooth, ATTR_DOMAIN_FACE>, | make_derived_read_attribute<MPoly, bool, get_shade_smooth>, | ||||
| make_derived_write_attribute<MPoly, | make_derived_write_attribute<MPoly, bool, get_shade_smooth, set_shade_smooth>, | ||||
| bool, | |||||
| get_shade_smooth, | |||||
| set_shade_smooth, | |||||
| ATTR_DOMAIN_FACE>, | |||||
| nullptr); | nullptr); | ||||
| static BuiltinCustomDataLayerProvider crease( | static BuiltinCustomDataLayerProvider crease( | ||||
| "crease", | "crease", | ||||
| ATTR_DOMAIN_EDGE, | ATTR_DOMAIN_EDGE, | ||||
| CD_PROP_FLOAT, | CD_PROP_FLOAT, | ||||
| CD_MEDGE, | CD_MEDGE, | ||||
| BuiltinAttributeProvider::NonCreatable, | BuiltinAttributeProvider::NonCreatable, | ||||
| BuiltinAttributeProvider::Writable, | BuiltinAttributeProvider::Writable, | ||||
| BuiltinAttributeProvider::NonDeletable, | BuiltinAttributeProvider::NonDeletable, | ||||
| edge_access, | edge_access, | ||||
| make_derived_read_attribute<MEdge, float, get_crease, ATTR_DOMAIN_EDGE>, | make_derived_read_attribute<MEdge, float, get_crease>, | ||||
| make_derived_write_attribute<MEdge, float, get_crease, set_crease, ATTR_DOMAIN_EDGE>, | make_derived_write_attribute<MEdge, float, get_crease, set_crease>, | ||||
| nullptr); | nullptr); | ||||
| static NamedLegacyCustomDataProvider uvs( | static NamedLegacyCustomDataProvider uvs( | ||||
| ATTR_DOMAIN_CORNER, | ATTR_DOMAIN_CORNER, | ||||
| CD_PROP_FLOAT2, | CD_PROP_FLOAT2, | ||||
| CD_MLOOPUV, | CD_MLOOPUV, | ||||
| corner_access, | corner_access, | ||||
| make_derived_read_attribute<MLoopUV, float2, get_loop_uv, ATTR_DOMAIN_CORNER>, | make_derived_read_attribute<MLoopUV, float2, get_loop_uv>, | ||||
| make_derived_write_attribute<MLoopUV, float2, get_loop_uv, set_loop_uv, ATTR_DOMAIN_CORNER>); | make_derived_write_attribute<MLoopUV, float2, get_loop_uv, set_loop_uv>); | ||||
| static NamedLegacyCustomDataProvider vertex_colors( | static NamedLegacyCustomDataProvider vertex_colors( | ||||
| ATTR_DOMAIN_CORNER, | ATTR_DOMAIN_CORNER, | ||||
| CD_PROP_COLOR, | CD_PROP_COLOR, | ||||
| CD_MLOOPCOL, | CD_MLOOPCOL, | ||||
| corner_access, | corner_access, | ||||
| make_derived_read_attribute<MLoopCol, Color4f, get_loop_color, ATTR_DOMAIN_CORNER>, | make_derived_read_attribute<MLoopCol, Color4f, get_loop_color>, | ||||
| make_derived_write_attribute<MLoopCol, | make_derived_write_attribute<MLoopCol, Color4f, get_loop_color, set_loop_color>); | ||||
| Color4f, | |||||
| get_loop_color, | |||||
| set_loop_color, | |||||
| ATTR_DOMAIN_CORNER>); | |||||
| static VertexGroupsAttributeProvider vertex_groups; | static VertexGroupsAttributeProvider vertex_groups; | ||||
| static CustomDataAttributeProvider corner_custom_data(ATTR_DOMAIN_CORNER, corner_access); | static CustomDataAttributeProvider corner_custom_data(ATTR_DOMAIN_CORNER, corner_access); | ||||
| static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access); | static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access); | ||||
| static CustomDataAttributeProvider edge_custom_data(ATTR_DOMAIN_EDGE, edge_access); | static CustomDataAttributeProvider edge_custom_data(ATTR_DOMAIN_EDGE, edge_access); | ||||
| static CustomDataAttributeProvider face_custom_data(ATTR_DOMAIN_FACE, face_access); | static CustomDataAttributeProvider face_custom_data(ATTR_DOMAIN_FACE, face_access); | ||||
| return ComponentAttributeProviders({&position, &material_index, &shade_smooth, &normal, &crease}, | return ComponentAttributeProviders({&position, &material_index, &shade_smooth, &normal, &crease}, | ||||
| Show All 19 Lines | |||||