diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 0f8c00dbe66..20fdecb1910 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -32,6 +32,7 @@ namespace blender::bke { class AttributeAccessor; class MutableAttributeAccessor; enum class AttrDomain : int8_t; +struct AttributeAccessorFunctions; } // namespace blender::bke namespace blender::bke::bake { struct BakeMaterialsList; @@ -1022,6 +1023,8 @@ inline float3 calculate_vector_handle(const float3 &point, const float3 &next_po /** \} */ +const AttributeAccessorFunctions &get_attribute_accessor_functions(); + } // namespace curves struct CurvesSurfaceTransforms { diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 0a78a49a0d8..cd0484b3ff1 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -31,7 +31,6 @@ namespace blender::bke { struct AttributeKind; class AttributeAccessor; struct AttributeMetaData; -class GeometryAttributeProviders; class CurvesEditHints; class Instances; class GeometryComponent; diff --git a/source/blender/blenkernel/BKE_grease_pencil.hh b/source/blender/blenkernel/BKE_grease_pencil.hh index 40586a48210..8dafa401a10 100644 --- a/source/blender/blenkernel/BKE_grease_pencil.hh +++ b/source/blender/blenkernel/BKE_grease_pencil.hh @@ -36,6 +36,7 @@ struct BakeMaterialsList; } namespace blender::bke { +struct AttributeAccessorFunctions; namespace greasepencil { @@ -882,6 +883,8 @@ inline LayerGroup &Layer::parent_group() TREENODE_COMMON_METHODS_FORWARD_IMPL(LayerGroup); +const AttributeAccessorFunctions &get_attribute_accessor_functions(); + } // namespace greasepencil class GreasePencilRuntime { diff --git a/source/blender/blenkernel/BKE_instances.hh b/source/blender/blenkernel/BKE_instances.hh index efc56981826..909c9670ecc 100644 --- a/source/blender/blenkernel/BKE_instances.hh +++ b/source/blender/blenkernel/BKE_instances.hh @@ -45,6 +45,7 @@ class MutableAttributeAccessor; namespace blender::bke { struct GeometrySet; +struct AttributeAccessorFunctions; /** * Holds a reference to conceptually unique geometry or a pointer to object/collection data @@ -229,6 +230,7 @@ class Instances { VArray instance_position_varray(const Instances &instances); VMutableArray instance_position_varray_for_write(Instances &instances); +const AttributeAccessorFunctions &instance_attribute_accessor_functions(); /* -------------------------------------------------------------------- */ /** \name #InstanceReference Inline Methods diff --git a/source/blender/blenkernel/BKE_mesh.hh b/source/blender/blenkernel/BKE_mesh.hh index 79866f9e3cd..947da0b6893 100644 --- a/source/blender/blenkernel/BKE_mesh.hh +++ b/source/blender/blenkernel/BKE_mesh.hh @@ -18,6 +18,7 @@ namespace blender::bke { enum class AttrDomain : int8_t; +struct AttributeAccessorFunctions; namespace mesh { /* -------------------------------------------------------------------- */ @@ -370,4 +371,6 @@ void mesh_data_update(Depsgraph &depsgraph, Object &ob, const CustomData_MeshMasks &dataMask); +const AttributeAccessorFunctions &mesh_attribute_accessor_functions(); + } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_pointcloud.hh b/source/blender/blenkernel/BKE_pointcloud.hh index 6f426e41dc9..31d47ad47e7 100644 --- a/source/blender/blenkernel/BKE_pointcloud.hh +++ b/source/blender/blenkernel/BKE_pointcloud.hh @@ -74,3 +74,8 @@ void BKE_pointcloud_batch_cache_free(PointCloud *pointcloud); extern void (*BKE_pointcloud_batch_cache_dirty_tag_cb)(PointCloud *pointcloud, int mode); extern void (*BKE_pointcloud_batch_cache_free_cb)(PointCloud *pointcloud); + +namespace blender::bke { +struct AttributeAccessorFunctions; +const AttributeAccessorFunctions &pointcloud_attribute_accessor_functions(); +} // namespace blender::bke diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 628a210cdfd..46bc5988d22 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -113,6 +113,7 @@ set(SRC intern/curve_to_mesh_convert.cc intern/curveprofile.cc intern/curves.cc + intern/curves_attributes.cc intern/curves_geometry.cc intern/curves_utils.cc intern/customdata.cc @@ -148,6 +149,7 @@ set(SRC intern/gpencil_legacy.cc intern/gpencil_modifier_legacy.cc intern/grease_pencil.cc + intern/grease_pencil_attributes.cc intern/grease_pencil_convert_legacy.cc intern/grease_pencil_vertex_groups.cc intern/icons.cc @@ -164,6 +166,7 @@ set(SRC intern/image_partial_update.cc intern/image_save.cc intern/instances.cc + intern/instances_attributes.cc intern/ipo.cc intern/kelvinlet.cc intern/key.cc @@ -195,6 +198,7 @@ set(SRC intern/mball.cc intern/mball_tessellate.cc intern/mesh.cc + intern/mesh_attributes.cc intern/mesh_calc_edges.cc intern/mesh_compare.cc intern/mesh_convert.cc @@ -263,6 +267,7 @@ set(SRC intern/pbvh_uv_islands.cc intern/pointcache.cc intern/pointcloud.cc + intern/pointcloud_attributes.cc intern/pose_backup.cc intern/preferences.cc intern/preview_image.cc diff --git a/source/blender/blenkernel/intern/curves_attributes.cc b/source/blender/blenkernel/intern/curves_attributes.cc new file mode 100644 index 00000000000..564e3f64a2a --- /dev/null +++ b/source/blender/blenkernel/intern/curves_attributes.cc @@ -0,0 +1,410 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_curves.hh" +#include "BKE_deform.hh" + +#include "FN_multi_function_builder.hh" + +#include "attribute_access_intern.hh" + +namespace blender::bke::curves { + +static void tag_component_topology_changed(void *owner) +{ + CurvesGeometry &curves = *static_cast(owner); + curves.tag_topology_changed(); +} + +static void tag_component_curve_types_changed(void *owner) +{ + CurvesGeometry &curves = *static_cast(owner); + curves.update_curve_types(); + curves.tag_topology_changed(); +} + +static void tag_component_positions_changed(void *owner) +{ + CurvesGeometry &curves = *static_cast(owner); + curves.tag_positions_changed(); +} + +static void tag_component_radii_changed(void *owner) +{ + CurvesGeometry &curves = *static_cast(owner); + curves.tag_radii_changed(); +} + +static void tag_component_normals_changed(void *owner) +{ + CurvesGeometry &curves = *static_cast(owner); + curves.tag_normals_changed(); +} + +/** + * This provider makes vertex groups available as float attributes. + */ +class CurvesVertexGroupsAttributeProvider final : public DynamicAttributesProvider { + public: + GAttributeReader try_get_for_read(const void *owner, const StringRef attribute_id) const final + { + if (bke::attribute_name_is_anonymous(attribute_id)) { + return {}; + } + const CurvesGeometry *curves = static_cast(owner); + if (curves == nullptr) { + return {}; + } + const int vertex_group_index = BKE_defgroup_name_index(&curves->vertex_group_names, + attribute_id); + if (vertex_group_index < 0) { + return {}; + } + const Span dverts = curves->deform_verts(); + return this->get_for_vertex_group_index(*curves, dverts, vertex_group_index); + } + + GAttributeReader get_for_vertex_group_index(const CurvesGeometry &curves, + const Span dverts, + const int vertex_group_index) const + { + BLI_assert(vertex_group_index >= 0); + if (dverts.is_empty()) { + return {VArray::ForSingle(0.0f, curves.points_num()), AttrDomain::Point}; + } + return {varray_for_deform_verts(dverts, vertex_group_index), AttrDomain::Point}; + } + + GAttributeWriter try_get_for_write(void *owner, const StringRef attribute_id) const final + { + if (bke::attribute_name_is_anonymous(attribute_id)) { + return {}; + } + CurvesGeometry *curves = static_cast(owner); + if (curves == nullptr) { + return {}; + } + const int vertex_group_index = BKE_defgroup_name_index(&curves->vertex_group_names, + attribute_id); + if (vertex_group_index < 0) { + return {}; + } + MutableSpan dverts = curves->deform_verts_for_write(); + return {varray_for_mutable_deform_verts(dverts, vertex_group_index), AttrDomain::Point}; + } + + bool try_delete(void *owner, const StringRef attribute_id) const final + { + if (bke::attribute_name_is_anonymous(attribute_id)) { + return false; + } + CurvesGeometry *curves = static_cast(owner); + if (curves == nullptr) { + return true; + } + const std::string name = attribute_id; + + int index; + bDeformGroup *group; + if (!BKE_defgroup_listbase_name_find( + &curves->vertex_group_names, name.c_str(), &index, &group)) + { + return false; + } + BLI_remlink(&curves->vertex_group_names, group); + MEM_freeN(group); + if (curves->deform_verts().is_empty()) { + return true; + } + + MutableSpan dverts = curves->deform_verts_for_write(); + remove_defgroup_index(dverts, index); + return true; + } + + bool foreach_attribute(const void *owner, + FunctionRef fn) const final + { + const CurvesGeometry *curves = static_cast(owner); + if (curves == nullptr) { + return true; + } + const Span dverts = curves->deform_verts(); + + int group_index = 0; + LISTBASE_FOREACH_INDEX (const bDeformGroup *, group, &curves->vertex_group_names, group_index) + { + const auto get_fn = [&]() { + return this->get_for_vertex_group_index(*curves, dverts, group_index); + }; + AttributeIter iter{group->name, AttrDomain::Point, CD_PROP_FLOAT, get_fn}; + fn(iter); + if (iter.is_stopped()) { + return false; + } + } + return true; + } + + void foreach_domain(const FunctionRef callback) const final + { + callback(AttrDomain::Point); + } +}; + +/** + * In this function all the attribute providers for a curves component are created. + * Most data in this function is statically allocated, because it does not change over time. + */ +static GeometryAttributeProviders create_attribute_providers_for_curve() +{ + static CustomDataAccessInfo curve_access = { + [](void *owner) -> CustomData * { + CurvesGeometry &curves = *static_cast(owner); + return &curves.curve_data; + }, + [](const void *owner) -> const CustomData * { + const CurvesGeometry &curves = *static_cast(owner); + return &curves.curve_data; + }, + [](const void *owner) -> int { + const CurvesGeometry &curves = *static_cast(owner); + return curves.curves_num(); + }}; + static CustomDataAccessInfo point_access = { + [](void *owner) -> CustomData * { + CurvesGeometry &curves = *static_cast(owner); + return &curves.point_data; + }, + [](const void *owner) -> const CustomData * { + const CurvesGeometry &curves = *static_cast(owner); + return &curves.point_data; + }, + [](const void *owner) -> int { + const CurvesGeometry &curves = *static_cast(owner); + return curves.points_num(); + }}; + + static BuiltinCustomDataLayerProvider position("position", + AttrDomain::Point, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::NonDeletable, + point_access, + tag_component_positions_changed); + + static BuiltinCustomDataLayerProvider radius("radius", + AttrDomain::Point, + CD_PROP_FLOAT, + BuiltinAttributeProvider::Deletable, + point_access, + tag_component_radii_changed); + + static BuiltinCustomDataLayerProvider id("id", + AttrDomain::Point, + CD_PROP_INT32, + BuiltinAttributeProvider::Deletable, + point_access, + nullptr); + + static BuiltinCustomDataLayerProvider tilt("tilt", + AttrDomain::Point, + CD_PROP_FLOAT, + BuiltinAttributeProvider::Deletable, + point_access, + tag_component_normals_changed); + + static BuiltinCustomDataLayerProvider handle_right("handle_right", + AttrDomain::Point, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::Deletable, + point_access, + tag_component_positions_changed); + + static BuiltinCustomDataLayerProvider handle_left("handle_left", + AttrDomain::Point, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::Deletable, + point_access, + tag_component_positions_changed); + + static auto handle_type_clamp = mf::build::SI1_SO( + "Handle Type Validate", + [](int8_t value) { + return std::clamp(value, BEZIER_HANDLE_FREE, BEZIER_HANDLE_ALIGN); + }, + mf::build::exec_presets::AllSpanOrSingle()); + static BuiltinCustomDataLayerProvider handle_type_right("handle_type_right", + AttrDomain::Point, + CD_PROP_INT8, + BuiltinAttributeProvider::Deletable, + point_access, + tag_component_topology_changed, + AttributeValidator{&handle_type_clamp}); + + static BuiltinCustomDataLayerProvider handle_type_left("handle_type_left", + AttrDomain::Point, + CD_PROP_INT8, + BuiltinAttributeProvider::Deletable, + point_access, + tag_component_topology_changed, + AttributeValidator{&handle_type_clamp}); + + static BuiltinCustomDataLayerProvider nurbs_weight("nurbs_weight", + AttrDomain::Point, + CD_PROP_FLOAT, + BuiltinAttributeProvider::Deletable, + point_access, + tag_component_positions_changed); + + static const auto nurbs_order_clamp = mf::build::SI1_SO( + "NURBS Order Validate", + [](int8_t value) { return std::max(value, 1); }, + mf::build::exec_presets::AllSpanOrSingle()); + static int nurbs_order_default = 4; + static BuiltinCustomDataLayerProvider nurbs_order("nurbs_order", + AttrDomain::Curve, + CD_PROP_INT8, + BuiltinAttributeProvider::Deletable, + curve_access, + tag_component_topology_changed, + AttributeValidator{&nurbs_order_clamp}, + &nurbs_order_default); + + static const auto normal_mode_clamp = mf::build::SI1_SO( + "Normal Mode Validate", + [](int8_t value) { + return std::clamp(value, NORMAL_MODE_MINIMUM_TWIST, NORMAL_MODE_FREE); + }, + mf::build::exec_presets::AllSpanOrSingle()); + static BuiltinCustomDataLayerProvider normal_mode("normal_mode", + AttrDomain::Curve, + CD_PROP_INT8, + BuiltinAttributeProvider::Deletable, + curve_access, + tag_component_normals_changed, + AttributeValidator{&normal_mode_clamp}); + + static BuiltinCustomDataLayerProvider custom_normal("custom_normal", + AttrDomain::Point, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::Deletable, + point_access, + tag_component_normals_changed); + + static const auto knots_mode_clamp = mf::build::SI1_SO( + "Knots Mode Validate", + [](int8_t value) { + return std::clamp(value, NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_ENDPOINT_BEZIER); + }, + mf::build::exec_presets::AllSpanOrSingle()); + static BuiltinCustomDataLayerProvider nurbs_knots_mode("knots_mode", + AttrDomain::Curve, + CD_PROP_INT8, + BuiltinAttributeProvider::Deletable, + curve_access, + tag_component_topology_changed, + AttributeValidator{&knots_mode_clamp}); + + static const auto curve_type_clamp = mf::build::SI1_SO( + "Curve Type Validate", + [](int8_t value) { + return std::clamp(value, CURVE_TYPE_CATMULL_ROM, CURVE_TYPES_NUM); + }, + mf::build::exec_presets::AllSpanOrSingle()); + static BuiltinCustomDataLayerProvider curve_type("curve_type", + AttrDomain::Curve, + CD_PROP_INT8, + BuiltinAttributeProvider::Deletable, + curve_access, + tag_component_curve_types_changed, + AttributeValidator{&curve_type_clamp}); + + static const auto resolution_clamp = mf::build::SI1_SO( + "Resolution Validate", + [](int value) { return std::max(value, 1); }, + mf::build::exec_presets::AllSpanOrSingle()); + static int resolution_default = 12; + static BuiltinCustomDataLayerProvider resolution("resolution", + AttrDomain::Curve, + CD_PROP_INT32, + BuiltinAttributeProvider::Deletable, + curve_access, + tag_component_topology_changed, + AttributeValidator{&resolution_clamp}, + &resolution_default); + + static BuiltinCustomDataLayerProvider cyclic("cyclic", + AttrDomain::Curve, + CD_PROP_BOOL, + BuiltinAttributeProvider::Deletable, + curve_access, + tag_component_topology_changed); + + static CurvesVertexGroupsAttributeProvider vertex_groups; + static CustomDataAttributeProvider curve_custom_data(AttrDomain::Curve, curve_access); + static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access); + + return GeometryAttributeProviders({&position, + &radius, + &id, + &tilt, + &handle_right, + &handle_left, + &handle_type_right, + &handle_type_left, + &normal_mode, + &custom_normal, + &nurbs_order, + &nurbs_knots_mode, + &nurbs_weight, + &curve_type, + &resolution, + &cyclic}, + {&vertex_groups, &curve_custom_data, &point_custom_data}); +} + +/** \} */ + +static AttributeAccessorFunctions get_curves_accessor_functions() +{ + static const GeometryAttributeProviders providers = create_attribute_providers_for_curve(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const AttrDomain domain) { + if (owner == nullptr) { + return 0; + } + const CurvesGeometry &curves = *static_cast(owner); + switch (domain) { + case AttrDomain::Point: + return curves.points_num(); + case AttrDomain::Curve: + return curves.curves_num(); + default: + return 0; + } + }; + fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { + return ELEM(domain, AttrDomain::Point, AttrDomain::Curve); + }; + fn.adapt_domain = [](const void *owner, + const GVArray &varray, + const AttrDomain from_domain, + const AttrDomain to_domain) -> GVArray { + if (owner == nullptr) { + return {}; + } + const CurvesGeometry &curves = *static_cast(owner); + return curves.adapt_domain(varray, from_domain, to_domain); + }; + return fn; +} + +const AttributeAccessorFunctions &get_attribute_accessor_functions() +{ + static const AttributeAccessorFunctions fn = get_curves_accessor_functions(); + return fn; +} + +} // namespace blender::bke::curves diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index e05029b02bd..cde365915eb 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -1569,6 +1569,16 @@ GVArray CurvesGeometry::adapt_domain(const GVArray &varray, return {}; } +AttributeAccessor CurvesGeometry::attributes() const +{ + return AttributeAccessor(this, curves::get_attribute_accessor_functions()); +} + +MutableAttributeAccessor CurvesGeometry::attributes_for_write() +{ + return MutableAttributeAccessor(this, curves::get_attribute_accessor_functions()); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc index 592b9464658..9c520904773 100644 --- a/source/blender/blenkernel/intern/geometry_component_curves.cc +++ b/source/blender/blenkernel/intern/geometry_component_curves.cc @@ -311,432 +311,20 @@ std::optional CurveLengthFieldInput::preferred_domain( /** \} */ /* -------------------------------------------------------------------- */ -/** \name Attribute Access Helper Functions +/** \name Attribute Access * \{ */ -static void tag_component_topology_changed(void *owner) -{ - CurvesGeometry &curves = *static_cast(owner); - curves.tag_topology_changed(); -} - -static void tag_component_curve_types_changed(void *owner) -{ - CurvesGeometry &curves = *static_cast(owner); - curves.update_curve_types(); - curves.tag_topology_changed(); -} - -static void tag_component_positions_changed(void *owner) -{ - CurvesGeometry &curves = *static_cast(owner); - curves.tag_positions_changed(); -} - -static void tag_component_radii_changed(void *owner) -{ - CurvesGeometry &curves = *static_cast(owner); - curves.tag_radii_changed(); -} - -static void tag_component_normals_changed(void *owner) -{ - CurvesGeometry &curves = *static_cast(owner); - curves.tag_normals_changed(); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Attribute Provider Declaration - * \{ */ - -/** - * This provider makes vertex groups available as float attributes. - */ -class CurvesVertexGroupsAttributeProvider final : public DynamicAttributesProvider { - public: - GAttributeReader try_get_for_read(const void *owner, const StringRef attribute_id) const final - { - if (bke::attribute_name_is_anonymous(attribute_id)) { - return {}; - } - const CurvesGeometry *curves = static_cast(owner); - if (curves == nullptr) { - return {}; - } - const int vertex_group_index = BKE_defgroup_name_index(&curves->vertex_group_names, - attribute_id); - if (vertex_group_index < 0) { - return {}; - } - const Span dverts = curves->deform_verts(); - return this->get_for_vertex_group_index(*curves, dverts, vertex_group_index); - } - - GAttributeReader get_for_vertex_group_index(const CurvesGeometry &curves, - const Span dverts, - const int vertex_group_index) const - { - BLI_assert(vertex_group_index >= 0); - if (dverts.is_empty()) { - return {VArray::ForSingle(0.0f, curves.points_num()), AttrDomain::Point}; - } - return {varray_for_deform_verts(dverts, vertex_group_index), AttrDomain::Point}; - } - - GAttributeWriter try_get_for_write(void *owner, const StringRef attribute_id) const final - { - if (bke::attribute_name_is_anonymous(attribute_id)) { - return {}; - } - CurvesGeometry *curves = static_cast(owner); - if (curves == nullptr) { - return {}; - } - const int vertex_group_index = BKE_defgroup_name_index(&curves->vertex_group_names, - attribute_id); - if (vertex_group_index < 0) { - return {}; - } - MutableSpan dverts = curves->deform_verts_for_write(); - return {varray_for_mutable_deform_verts(dverts, vertex_group_index), AttrDomain::Point}; - } - - bool try_delete(void *owner, const StringRef attribute_id) const final - { - if (bke::attribute_name_is_anonymous(attribute_id)) { - return false; - } - CurvesGeometry *curves = static_cast(owner); - if (curves == nullptr) { - return true; - } - const std::string name = attribute_id; - - int index; - bDeformGroup *group; - if (!BKE_defgroup_listbase_name_find( - &curves->vertex_group_names, name.c_str(), &index, &group)) - { - return false; - } - BLI_remlink(&curves->vertex_group_names, group); - MEM_freeN(group); - if (curves->deform_verts().is_empty()) { - return true; - } - - MutableSpan dverts = curves->deform_verts_for_write(); - remove_defgroup_index(dverts, index); - return true; - } - - bool foreach_attribute(const void *owner, - FunctionRef fn) const final - { - const CurvesGeometry *curves = static_cast(owner); - if (curves == nullptr) { - return true; - } - const Span dverts = curves->deform_verts(); - - int group_index = 0; - LISTBASE_FOREACH_INDEX (const bDeformGroup *, group, &curves->vertex_group_names, group_index) - { - const auto get_fn = [&]() { - return this->get_for_vertex_group_index(*curves, dverts, group_index); - }; - AttributeIter iter{group->name, AttrDomain::Point, CD_PROP_FLOAT, get_fn}; - fn(iter); - if (iter.is_stopped()) { - return false; - } - } - return true; - } - - void foreach_domain(const FunctionRef callback) const final - { - callback(AttrDomain::Point); - } -}; - -/** - * In this function all the attribute providers for a curves component are created. - * Most data in this function is statically allocated, because it does not change over time. - */ -static GeometryAttributeProviders create_attribute_providers_for_curve() -{ - static CustomDataAccessInfo curve_access = { - [](void *owner) -> CustomData * { - CurvesGeometry &curves = *static_cast(owner); - return &curves.curve_data; - }, - [](const void *owner) -> const CustomData * { - const CurvesGeometry &curves = *static_cast(owner); - return &curves.curve_data; - }, - [](const void *owner) -> int { - const CurvesGeometry &curves = *static_cast(owner); - return curves.curves_num(); - }}; - static CustomDataAccessInfo point_access = { - [](void *owner) -> CustomData * { - CurvesGeometry &curves = *static_cast(owner); - return &curves.point_data; - }, - [](const void *owner) -> const CustomData * { - const CurvesGeometry &curves = *static_cast(owner); - return &curves.point_data; - }, - [](const void *owner) -> int { - const CurvesGeometry &curves = *static_cast(owner); - return curves.points_num(); - }}; - - static BuiltinCustomDataLayerProvider position("position", - AttrDomain::Point, - CD_PROP_FLOAT3, - BuiltinAttributeProvider::NonDeletable, - point_access, - tag_component_positions_changed); - - static BuiltinCustomDataLayerProvider radius("radius", - AttrDomain::Point, - CD_PROP_FLOAT, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_radii_changed); - - static BuiltinCustomDataLayerProvider id("id", - AttrDomain::Point, - CD_PROP_INT32, - BuiltinAttributeProvider::Deletable, - point_access, - nullptr); - - static BuiltinCustomDataLayerProvider tilt("tilt", - AttrDomain::Point, - CD_PROP_FLOAT, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_normals_changed); - - static BuiltinCustomDataLayerProvider handle_right("handle_right", - AttrDomain::Point, - CD_PROP_FLOAT3, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_positions_changed); - - static BuiltinCustomDataLayerProvider handle_left("handle_left", - AttrDomain::Point, - CD_PROP_FLOAT3, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_positions_changed); - - static auto handle_type_clamp = mf::build::SI1_SO( - "Handle Type Validate", - [](int8_t value) { - return std::clamp(value, BEZIER_HANDLE_FREE, BEZIER_HANDLE_ALIGN); - }, - mf::build::exec_presets::AllSpanOrSingle()); - static BuiltinCustomDataLayerProvider handle_type_right("handle_type_right", - AttrDomain::Point, - CD_PROP_INT8, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_topology_changed, - AttributeValidator{&handle_type_clamp}); - - static BuiltinCustomDataLayerProvider handle_type_left("handle_type_left", - AttrDomain::Point, - CD_PROP_INT8, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_topology_changed, - AttributeValidator{&handle_type_clamp}); - - static BuiltinCustomDataLayerProvider nurbs_weight("nurbs_weight", - AttrDomain::Point, - CD_PROP_FLOAT, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_positions_changed); - - static const auto nurbs_order_clamp = mf::build::SI1_SO( - "NURBS Order Validate", - [](int8_t value) { return std::max(value, 1); }, - mf::build::exec_presets::AllSpanOrSingle()); - static int nurbs_order_default = 4; - static BuiltinCustomDataLayerProvider nurbs_order("nurbs_order", - AttrDomain::Curve, - CD_PROP_INT8, - BuiltinAttributeProvider::Deletable, - curve_access, - tag_component_topology_changed, - AttributeValidator{&nurbs_order_clamp}, - &nurbs_order_default); - - static const auto normal_mode_clamp = mf::build::SI1_SO( - "Normal Mode Validate", - [](int8_t value) { - return std::clamp(value, NORMAL_MODE_MINIMUM_TWIST, NORMAL_MODE_FREE); - }, - mf::build::exec_presets::AllSpanOrSingle()); - static BuiltinCustomDataLayerProvider normal_mode("normal_mode", - AttrDomain::Curve, - CD_PROP_INT8, - BuiltinAttributeProvider::Deletable, - curve_access, - tag_component_normals_changed, - AttributeValidator{&normal_mode_clamp}); - - static BuiltinCustomDataLayerProvider custom_normal("custom_normal", - AttrDomain::Point, - CD_PROP_FLOAT3, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_normals_changed); - - static const auto knots_mode_clamp = mf::build::SI1_SO( - "Knots Mode Validate", - [](int8_t value) { - return std::clamp(value, NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_ENDPOINT_BEZIER); - }, - mf::build::exec_presets::AllSpanOrSingle()); - static BuiltinCustomDataLayerProvider nurbs_knots_mode("knots_mode", - AttrDomain::Curve, - CD_PROP_INT8, - BuiltinAttributeProvider::Deletable, - curve_access, - tag_component_topology_changed, - AttributeValidator{&knots_mode_clamp}); - - static const auto curve_type_clamp = mf::build::SI1_SO( - "Curve Type Validate", - [](int8_t value) { - return std::clamp(value, CURVE_TYPE_CATMULL_ROM, CURVE_TYPES_NUM); - }, - mf::build::exec_presets::AllSpanOrSingle()); - static BuiltinCustomDataLayerProvider curve_type("curve_type", - AttrDomain::Curve, - CD_PROP_INT8, - BuiltinAttributeProvider::Deletable, - curve_access, - tag_component_curve_types_changed, - AttributeValidator{&curve_type_clamp}); - - static const auto resolution_clamp = mf::build::SI1_SO( - "Resolution Validate", - [](int value) { return std::max(value, 1); }, - mf::build::exec_presets::AllSpanOrSingle()); - static int resolution_default = 12; - static BuiltinCustomDataLayerProvider resolution("resolution", - AttrDomain::Curve, - CD_PROP_INT32, - BuiltinAttributeProvider::Deletable, - curve_access, - tag_component_topology_changed, - AttributeValidator{&resolution_clamp}, - &resolution_default); - - static BuiltinCustomDataLayerProvider cyclic("cyclic", - AttrDomain::Curve, - CD_PROP_BOOL, - BuiltinAttributeProvider::Deletable, - curve_access, - tag_component_topology_changed); - - static CurvesVertexGroupsAttributeProvider vertex_groups; - static CustomDataAttributeProvider curve_custom_data(AttrDomain::Curve, curve_access); - static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access); - - return GeometryAttributeProviders({&position, - &radius, - &id, - &tilt, - &handle_right, - &handle_left, - &handle_type_right, - &handle_type_left, - &normal_mode, - &custom_normal, - &nurbs_order, - &nurbs_knots_mode, - &nurbs_weight, - &curve_type, - &resolution, - &cyclic}, - {&vertex_groups, &curve_custom_data, &point_custom_data}); -} - -/** \} */ - -static AttributeAccessorFunctions get_curves_accessor_functions() -{ - static const GeometryAttributeProviders providers = create_attribute_providers_for_curve(); - AttributeAccessorFunctions fn = - attribute_accessor_functions::accessor_functions_for_providers(); - fn.domain_size = [](const void *owner, const AttrDomain domain) { - if (owner == nullptr) { - return 0; - } - const CurvesGeometry &curves = *static_cast(owner); - switch (domain) { - case AttrDomain::Point: - return curves.points_num(); - case AttrDomain::Curve: - return curves.curves_num(); - default: - return 0; - } - }; - fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { - return ELEM(domain, AttrDomain::Point, AttrDomain::Curve); - }; - fn.adapt_domain = [](const void *owner, - const GVArray &varray, - const AttrDomain from_domain, - const AttrDomain to_domain) -> GVArray { - if (owner == nullptr) { - return {}; - } - const CurvesGeometry &curves = *static_cast(owner); - return curves.adapt_domain(varray, from_domain, to_domain); - }; - return fn; -} - -static const AttributeAccessorFunctions &get_curves_accessor_functions_ref() -{ - static const AttributeAccessorFunctions fn = get_curves_accessor_functions(); - return fn; -} - -AttributeAccessor CurvesGeometry::attributes() const -{ - return AttributeAccessor(this, get_curves_accessor_functions_ref()); -} - -MutableAttributeAccessor CurvesGeometry::attributes_for_write() -{ - return MutableAttributeAccessor(this, get_curves_accessor_functions_ref()); -} - std::optional CurveComponent::attributes() const { return AttributeAccessor(curves_ ? &curves_->geometry : nullptr, - get_curves_accessor_functions_ref()); + curves::get_attribute_accessor_functions()); } std::optional CurveComponent::attributes_for_write() { Curves *curves = this->get_for_write(); return MutableAttributeAccessor(curves ? &curves->geometry : nullptr, - get_curves_accessor_functions_ref()); + curves::get_attribute_accessor_functions()); } } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_grease_pencil.cc b/source/blender/blenkernel/intern/geometry_component_grease_pencil.cc index c08724d1f0d..b9866d96328 100644 --- a/source/blender/blenkernel/intern/geometry_component_grease_pencil.cc +++ b/source/blender/blenkernel/intern/geometry_component_grease_pencil.cc @@ -101,103 +101,19 @@ void GreasePencilComponent::ensure_owns_direct_data() } } -static GeometryAttributeProviders create_attribute_providers_for_grease_pencil() -{ - static CustomDataAccessInfo layers_access = { - [](void *owner) -> CustomData * { - GreasePencil &grease_pencil = *static_cast(owner); - return &grease_pencil.layers_data; - }, - [](const void *owner) -> const CustomData * { - const GreasePencil &grease_pencil = *static_cast(owner); - return &grease_pencil.layers_data; - }, - [](const void *owner) -> int { - const GreasePencil &grease_pencil = *static_cast(owner); - return grease_pencil.layers().size(); - }}; - - static CustomDataAttributeProvider layer_custom_data(AttrDomain::Layer, layers_access); - - return GeometryAttributeProviders({}, {&layer_custom_data}); -} - -static GVArray adapt_grease_pencil_attribute_domain(const GreasePencil & /*grease_pencil*/, - const GVArray &varray, - const AttrDomain from, - const AttrDomain to) -{ - if (from == to) { - return varray; - } - return {}; -} - -static AttributeAccessorFunctions get_grease_pencil_accessor_functions() -{ - static const GeometryAttributeProviders providers = - create_attribute_providers_for_grease_pencil(); - AttributeAccessorFunctions fn = - attribute_accessor_functions::accessor_functions_for_providers(); - fn.domain_size = [](const void *owner, const AttrDomain domain) { - if (owner == nullptr) { - return 0; - } - const GreasePencil &grease_pencil = *static_cast(owner); - switch (domain) { - case AttrDomain::Layer: - return int(grease_pencil.layers().size()); - default: - return 0; - } - }; - fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { - return domain == AttrDomain::Layer; - }; - fn.adapt_domain = [](const void *owner, - const GVArray &varray, - const AttrDomain from_domain, - const AttrDomain to_domain) -> GVArray { - if (owner == nullptr) { - return {}; - } - const GreasePencil &grease_pencil = *static_cast(owner); - return adapt_grease_pencil_attribute_domain(grease_pencil, varray, from_domain, to_domain); - }; - return fn; -} - -static const AttributeAccessorFunctions &get_grease_pencil_accessor_functions_ref() -{ - static const AttributeAccessorFunctions fn = get_grease_pencil_accessor_functions(); - return fn; -} - } // namespace blender::bke -blender::bke::AttributeAccessor GreasePencil::attributes() const -{ - return blender::bke::AttributeAccessor(this, - blender::bke::get_grease_pencil_accessor_functions_ref()); -} - -blender::bke::MutableAttributeAccessor GreasePencil::attributes_for_write() -{ - return blender::bke::MutableAttributeAccessor( - this, blender::bke::get_grease_pencil_accessor_functions_ref()); -} - namespace blender::bke { std::optional GreasePencilComponent::attributes() const { - return AttributeAccessor(grease_pencil_, get_grease_pencil_accessor_functions_ref()); + return AttributeAccessor(grease_pencil_, greasepencil::get_attribute_accessor_functions()); } std::optional GreasePencilComponent::attributes_for_write() { GreasePencil *grease_pencil = this->get_for_write(); - return MutableAttributeAccessor(grease_pencil, get_grease_pencil_accessor_functions_ref()); + return MutableAttributeAccessor(grease_pencil, greasepencil::get_attribute_accessor_functions()); } } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index c6602286e44..844cce20496 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -117,119 +117,14 @@ void InstancesComponent::count_memory(MemoryCounter &memory) const } } -static void tag_component_reference_index_changed(void *owner) -{ - Instances &instances = *static_cast(owner); - instances.tag_reference_handles_changed(); -} - -static GeometryAttributeProviders create_attribute_providers_for_instances() -{ - static CustomDataAccessInfo instance_custom_data_access = { - [](void *owner) -> CustomData * { - Instances *instances = static_cast(owner); - return &instances->custom_data_attributes(); - }, - [](const void *owner) -> const CustomData * { - const Instances *instances = static_cast(owner); - return &instances->custom_data_attributes(); - }, - [](const void *owner) -> int { - const Instances *instances = static_cast(owner); - return instances->instances_num(); - }}; - - /** - * IDs of the instances. They are used for consistency over multiple frames for things like - * motion blur. Proper stable ID data that actually helps when rendering can only be generated - * in some situations, so this vector is allowed to be empty, in which case the index of each - * instance will be used for the final ID. - */ - static BuiltinCustomDataLayerProvider id("id", - AttrDomain::Instance, - CD_PROP_INT32, - BuiltinAttributeProvider::Deletable, - instance_custom_data_access, - nullptr); - - static BuiltinCustomDataLayerProvider instance_transform("instance_transform", - AttrDomain::Instance, - CD_PROP_FLOAT4X4, - BuiltinAttributeProvider::NonDeletable, - instance_custom_data_access, - nullptr); - - /** Indices into `Instances::references_`. Determines what data is instanced. */ - static BuiltinCustomDataLayerProvider reference_index(".reference_index", - AttrDomain::Instance, - CD_PROP_INT32, - BuiltinAttributeProvider::NonDeletable, - instance_custom_data_access, - tag_component_reference_index_changed); - - static CustomDataAttributeProvider instance_custom_data(AttrDomain::Instance, - instance_custom_data_access); - - return GeometryAttributeProviders({&instance_transform, &id, &reference_index}, - {&instance_custom_data}); -} - -static AttributeAccessorFunctions get_instances_accessor_functions() -{ - static const GeometryAttributeProviders providers = create_attribute_providers_for_instances(); - AttributeAccessorFunctions fn = - attribute_accessor_functions::accessor_functions_for_providers(); - fn.domain_size = [](const void *owner, const AttrDomain domain) { - if (owner == nullptr) { - return 0; - } - const Instances *instances = static_cast(owner); - switch (domain) { - case AttrDomain::Instance: - return instances->instances_num(); - default: - return 0; - } - }; - fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { - return domain == AttrDomain::Instance; - }; - fn.adapt_domain = [](const void * /*owner*/, - const GVArray &varray, - const AttrDomain from_domain, - const AttrDomain to_domain) { - if (from_domain == to_domain && from_domain == AttrDomain::Instance) { - return varray; - } - return GVArray{}; - }; - return fn; -} - -static const AttributeAccessorFunctions &get_instances_accessor_functions_ref() -{ - static const AttributeAccessorFunctions fn = get_instances_accessor_functions(); - return fn; -} - -AttributeAccessor Instances::attributes() const -{ - return AttributeAccessor(this, get_instances_accessor_functions_ref()); -} - -MutableAttributeAccessor Instances::attributes_for_write() -{ - return MutableAttributeAccessor(this, get_instances_accessor_functions_ref()); -} - std::optional InstancesComponent::attributes() const { - return AttributeAccessor(instances_, get_instances_accessor_functions_ref()); + return AttributeAccessor(instances_, instance_attribute_accessor_functions()); } std::optional InstancesComponent::attributes_for_write() { - return MutableAttributeAccessor(instances_, get_instances_accessor_functions_ref()); + return MutableAttributeAccessor(instances_, instance_attribute_accessor_functions()); } /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 484974fd0ca..3713a1a8764 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -171,1029 +171,23 @@ VArray mesh_normals_varray(const Mesh &mesh, /** \} */ +} // namespace blender::bke + +namespace blender::bke { + /* -------------------------------------------------------------------- */ /** \name Attribute Access * \{ */ -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.verts_num); - const Span corner_verts = mesh.corner_verts(); - - attribute_math::DefaultMixer mixer(r_values); - for (const int corner : IndexRange(mesh.corners_num)) { - mixer.mix_in(corner_verts[corner], old_values[corner]); - } - 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.verts_num); - const Span corner_verts = mesh.corner_verts(); - - r_values.fill(true); - for (const int corner : IndexRange(mesh.corners_num)) { - const int point_index = corner_verts[corner]; - - if (!old_values[corner]) { - r_values[point_index] = false; - } - } - - /* Deselect loose vertices without corners that are still selected from the 'true' default. */ - const LooseVertCache &loose_verts = mesh.verts_no_face(); - if (loose_verts.count > 0) { - const BitSpan bits = loose_verts.is_loose_bits; - threading::parallel_for(bits.index_range(), 2048, [&](const IndexRange range) { - for (const int vert_index : range) { - if (bits[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.verts_num); - 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 corner_verts = mesh.corner_verts(); - - GVArray new_varray; - attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { - using T = decltype(dummy); - new_varray = VArray::ForFunc( - mesh.corners_num, [corner_verts, varray = varray.typed()](const int64_t corner) { - return varray[corner_verts[corner]]; - }); - }); - return new_varray; -} - -static GVArray adapt_mesh_domain_corner_to_face(const Mesh &mesh, const GVArray &varray) -{ - const OffsetIndices faces = mesh.faces(); - - 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( - faces.size(), [faces, varray = varray.typed()](const int face_index) { - /* A face is selected if all of its corners were selected. */ - for (const int loop_index : faces[face_index]) { - if (!varray[loop_index]) { - return false; - } - } - return true; - }); - } - else { - new_varray = VArray::ForFunc( - faces.size(), [faces, varray = varray.typed()](const int face_index) { - T return_value; - attribute_math::DefaultMixer mixer({&return_value, 1}); - for (const int loop_index : faces[face_index]) { - 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.edges_num); - const OffsetIndices faces = mesh.faces(); - const Span corner_edges = mesh.corner_edges(); - - attribute_math::DefaultMixer mixer(r_values); - - for (const int face_index : faces.index_range()) { - const IndexRange face = faces[face_index]; - - /* For every edge, mix values from the two adjacent corners (the current and next corner). */ - for (const int corner : face) { - const int next_corner = mesh::face_corner_next(face, corner); - const int edge_index = corner_edges[corner]; - mixer.mix_in(edge_index, old_values[corner]); - mixer.mix_in(edge_index, old_values[next_corner]); - } - } - - 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.edges_num); - const OffsetIndices faces = mesh.faces(); - const Span corner_edges = mesh.corner_edges(); - - r_values.fill(true); - for (const int face_index : faces.index_range()) { - const IndexRange face = faces[face_index]; - - for (const int corner : face) { - const int next_corner = mesh::face_corner_next(face, corner); - const int edge_index = corner_edges[corner]; - if (!old_values[corner] || !old_values[next_corner]) { - r_values[edge_index] = false; - } - } - } - - const LooseEdgeCache &loose_edges = mesh.loose_edges(); - if (loose_edges.count > 0) { - /* Deselect loose edges without corners that are still selected from the 'true' default. */ - threading::parallel_for(IndexRange(mesh.edges_num), 2048, [&](const IndexRange range) { - for (const int edge_index : range) { - if (loose_edges.is_loose_bits[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.edges_num); - 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.verts_num); - const OffsetIndices faces = mesh.faces(); - const Span corner_verts = mesh.corner_verts(); - - attribute_math::DefaultMixer mixer(r_values); - - for (const int face_index : faces.index_range()) { - const T value = old_values[face_index]; - for (const int vert : corner_verts.slice(faces[face_index])) { - mixer.mix_in(vert, 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.verts_num); - const OffsetIndices faces = mesh.faces(); - const Span corner_verts = mesh.corner_verts(); - - r_values.fill(false); - threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) { - for (const int face_index : range) { - if (old_values[face_index]) { - for (const int vert : corner_verts.slice(faces[face_index])) { - r_values[vert] = true; - } - } - } - }); -} - -static GVArray adapt_mesh_domain_face_to_point(const Mesh &mesh, const GVArray &varray) -{ - GArray<> values(varray.type(), mesh.verts_num); - 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.corners_num); - const OffsetIndices faces = mesh.faces(); - - threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) { - for (const int face_index : range) { - MutableSpan face_corner_values = r_values.slice(faces[face_index]); - face_corner_values.fill(old_values[face_index]); - } - }); -} - -static GVArray adapt_mesh_domain_face_to_corner(const Mesh &mesh, const GVArray &varray) -{ - GArray<> values(varray.type(), mesh.corners_num); - 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.edges_num); - const OffsetIndices faces = mesh.faces(); - const Span corner_edges = mesh.corner_edges(); - - attribute_math::DefaultMixer mixer(r_values); - - for (const int face_index : faces.index_range()) { - const T value = old_values[face_index]; - for (const int edge : corner_edges.slice(faces[face_index])) { - mixer.mix_in(edge, 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.edges_num); - const OffsetIndices faces = mesh.faces(); - const Span corner_edges = mesh.corner_edges(); - - r_values.fill(false); - threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) { - for (const int face_index : range) { - if (old_values[face_index]) { - for (const int edge : corner_edges.slice(faces[face_index])) { - r_values[edge] = true; - } - } - } - }); -} - -static GVArray adapt_mesh_domain_face_to_edge(const Mesh &mesh, const GVArray &varray) -{ - GArray<> values(varray.type(), mesh.edges_num); - 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 OffsetIndices faces = mesh.faces(); - const Span corner_verts = mesh.corner_verts(); - - 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.faces_num, - [corner_verts, faces, varray = varray.typed()](const int face_index) { - /* A face is selected if all of its vertices were selected. */ - for (const int vert : corner_verts.slice(faces[face_index])) { - if (!varray[vert]) { - return false; - } - } - return true; - }); - } - else { - new_varray = VArray::ForFunc( - mesh.faces_num, - [corner_verts, faces, varray = varray.typed()](const int face_index) { - T return_value; - attribute_math::DefaultMixer mixer({&return_value, 1}); - for (const int vert : corner_verts.slice(faces[face_index])) { - mixer.mix_in(0, varray[vert]); - } - 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 int2 &edge = edges[edge_index]; - return varray[edge[0]] && varray[edge[1]]; - }); - } - 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 int2 &edge = edges[edge_index]; - mixer.mix_in(0, varray[edge[0]]); - mixer.mix_in(0, varray[edge[1]]); - 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.corners_num); - const OffsetIndices faces = mesh.faces(); - const Span corner_edges = mesh.corner_edges(); - - attribute_math::DefaultMixer mixer(r_values); - - for (const int face_index : faces.index_range()) { - const IndexRange face = faces[face_index]; - - /* For every corner, mix the values from the adjacent edges on the face. */ - for (const int loop_index : face) { - const int loop_index_prev = mesh::face_corner_prev(face, loop_index); - const int edge = corner_edges[loop_index]; - const int edge_prev = corner_edges[loop_index_prev]; - mixer.mix_in(loop_index, old_values[edge]); - mixer.mix_in(loop_index, old_values[edge_prev]); - } - } - - 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.corners_num); - const OffsetIndices faces = mesh.faces(); - const Span corner_edges = mesh.corner_edges(); - - r_values.fill(false); - - threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) { - for (const int face_index : range) { - const IndexRange face = faces[face_index]; - for (const int loop_index : face) { - const int loop_index_prev = mesh::face_corner_prev(face, loop_index); - const int edge = corner_edges[loop_index]; - const int edge_prev = corner_edges[loop_index_prev]; - if (old_values[edge] && old_values[edge_prev]) { - r_values[loop_index] = true; - } - } - } - }); -} - -static GVArray adapt_mesh_domain_edge_to_corner(const Mesh &mesh, const GVArray &varray) -{ - GArray<> values(varray.type(), mesh.corners_num); - 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.verts_num); - const Span edges = mesh.edges(); - - attribute_math::DefaultMixer mixer(r_values); - - for (const int edge_index : IndexRange(mesh.edges_num)) { - const int2 &edge = edges[edge_index]; - const T value = old_values[edge_index]; - mixer.mix_in(edge[0], value); - mixer.mix_in(edge[1], 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.verts_num); - const Span edges = mesh.edges(); - - /* Multiple threads can write to the same index here, but they are only - * writing true, and writing to single bytes is expected to be threadsafe. */ - r_values.fill(false); - threading::parallel_for(edges.index_range(), 4096, [&](const IndexRange range) { - for (const int edge_index : range) { - if (old_values[edge_index]) { - const int2 &edge = edges[edge_index]; - r_values[edge[0]] = true; - r_values[edge[1]] = true; - } - } - }); -} - -static GVArray adapt_mesh_domain_edge_to_point(const Mesh &mesh, const GVArray &varray) -{ - GArray<> values(varray.type(), mesh.verts_num); - 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 OffsetIndices faces = mesh.faces(); - const Span corner_edges = mesh.corner_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) { - /* A face is selected if all of its edges are selected. */ - new_varray = VArray::ForFunc( - faces.size(), [corner_edges, faces, varray = varray.typed()](const int face_index) { - for (const int edge : corner_edges.slice(faces[face_index])) { - if (!varray[edge]) { - return false; - } - } - return true; - }); - } - else { - new_varray = VArray::ForFunc( - faces.size(), [corner_edges, faces, varray = varray.typed()](const int face_index) { - T return_value; - attribute_math::DefaultMixer mixer({&return_value, 1}); - for (const int edge : corner_edges.slice(faces[face_index])) { - mixer.mix_in(0, varray[edge]); - } - mixer.finalize(); - return return_value; - }); - } - } - }); - return new_varray; -} - -static bool can_simple_adapt_for_single(const Mesh &mesh, - const AttrDomain from_domain, - const AttrDomain 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 AttrDomain::Point: - /* All other domains are always connected to points. */ - return true; - case AttrDomain::Edge: - if (to_domain == AttrDomain::Point) { - return mesh.loose_verts().count == 0; - } - return true; - case AttrDomain::Face: - if (to_domain == AttrDomain::Point) { - return mesh.verts_no_face().count == 0; - } - if (to_domain == AttrDomain::Edge) { - return mesh.loose_edges().count == 0; - } - return true; - case AttrDomain::Corner: - if (to_domain == AttrDomain::Point) { - return mesh.verts_no_face().count == 0; - } - if (to_domain == AttrDomain::Edge) { - return mesh.loose_edges().count == 0; - } - return true; - default: - BLI_assert_unreachable(); - return false; - } -} - -static GVArray adapt_mesh_attribute_domain(const Mesh &mesh, - const GVArray &varray, - const AttrDomain from_domain, - const AttrDomain to_domain) -{ - if (!varray) { - return {}; - } - if (varray.is_empty()) { - return {}; - } - if (from_domain == to_domain) { - return varray; - } - if (varray.is_single()) { - if (can_simple_adapt_for_single(mesh, from_domain, to_domain)) { - BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), value); - varray.get_internal_single(value); - return GVArray::ForSingle(varray.type(), mesh.attributes().domain_size(to_domain), value); - } - } - - switch (from_domain) { - case AttrDomain::Corner: { - switch (to_domain) { - case AttrDomain::Point: - return adapt_mesh_domain_corner_to_point(mesh, varray); - case AttrDomain::Face: - return adapt_mesh_domain_corner_to_face(mesh, varray); - case AttrDomain::Edge: - return adapt_mesh_domain_corner_to_edge(mesh, varray); - default: - break; - } - break; - } - case AttrDomain::Point: { - switch (to_domain) { - case AttrDomain::Corner: - return adapt_mesh_domain_point_to_corner(mesh, varray); - case AttrDomain::Face: - return adapt_mesh_domain_point_to_face(mesh, varray); - case AttrDomain::Edge: - return adapt_mesh_domain_point_to_edge(mesh, varray); - default: - break; - } - break; - } - case AttrDomain::Face: { - switch (to_domain) { - case AttrDomain::Point: - return adapt_mesh_domain_face_to_point(mesh, varray); - case AttrDomain::Corner: - return adapt_mesh_domain_face_to_corner(mesh, varray); - case AttrDomain::Edge: - return adapt_mesh_domain_face_to_edge(mesh, varray); - default: - break; - } - break; - } - case AttrDomain::Edge: { - switch (to_domain) { - case AttrDomain::Corner: - return adapt_mesh_domain_edge_to_corner(mesh, varray); - case AttrDomain::Point: - return adapt_mesh_domain_edge_to_point(mesh, varray); - case AttrDomain::Face: - return adapt_mesh_domain_edge_to_face(mesh, varray); - default: - break; - } - break; - } - default: - break; - } - - return {}; -} - -static void tag_component_positions_changed(void *owner) -{ - Mesh *mesh = static_cast(owner); - if (mesh != nullptr) { - mesh->tag_positions_changed(); - } -} - -static void tag_component_sharpness_changed(void *owner) -{ - if (Mesh *mesh = static_cast(owner)) { - mesh->tag_sharpness_changed(); - } -} - -/** - * This provider makes vertex groups available as float attributes. - */ -class MeshVertexGroupsAttributeProvider final : public DynamicAttributesProvider { - public: - GAttributeReader try_get_for_read(const void *owner, const StringRef attribute_id) const final - { - if (bke::attribute_name_is_anonymous(attribute_id)) { - return {}; - } - const Mesh *mesh = static_cast(owner); - if (mesh == nullptr) { - return {}; - } - const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names, - attribute_id); - if (vertex_group_index < 0) { - return {}; - } - const Span dverts = mesh->deform_verts(); - return this->get_for_vertex_group_index(*mesh, dverts, vertex_group_index); - } - - GAttributeReader get_for_vertex_group_index(const Mesh &mesh, - const Span dverts, - const int vertex_group_index) const - { - BLI_assert(vertex_group_index >= 0); - if (dverts.is_empty()) { - return {VArray::ForSingle(0.0f, mesh.verts_num), AttrDomain::Point}; - } - return {varray_for_deform_verts(dverts, vertex_group_index), AttrDomain::Point}; - } - - GAttributeWriter try_get_for_write(void *owner, const StringRef attribute_id) const final - { - if (bke::attribute_name_is_anonymous(attribute_id)) { - return {}; - } - Mesh *mesh = static_cast(owner); - if (mesh == nullptr) { - return {}; - } - - const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names, - attribute_id); - if (vertex_group_index < 0) { - return {}; - } - MutableSpan dverts = mesh->deform_verts_for_write(); - return {varray_for_mutable_deform_verts(dverts, vertex_group_index), AttrDomain::Point}; - } - - bool try_delete(void *owner, const StringRef attribute_id) const final - { - if (bke::attribute_name_is_anonymous(attribute_id)) { - return false; - } - Mesh *mesh = static_cast(owner); - if (mesh == nullptr) { - return true; - } - - const std::string name = attribute_id; - - int index; - bDeformGroup *group; - if (!BKE_id_defgroup_name_find(&mesh->id, name.c_str(), &index, &group)) { - return false; - } - BLI_remlink(&mesh->vertex_group_names, group); - MEM_freeN(group); - if (mesh->deform_verts().is_empty()) { - return true; - } - - MutableSpan dverts = mesh->deform_verts_for_write(); - remove_defgroup_index(dverts, index); - return true; - } - - bool foreach_attribute(const void *owner, - const FunctionRef fn) const final - { - const Mesh *mesh = static_cast(owner); - if (mesh == nullptr) { - return true; - } - - const Span dverts = mesh->deform_verts(); - - int group_index = 0; - LISTBASE_FOREACH_INDEX (const bDeformGroup *, group, &mesh->vertex_group_names, group_index) { - const auto get_fn = [&]() { - return this->get_for_vertex_group_index(*mesh, dverts, group_index); - }; - - AttributeIter iter{group->name, AttrDomain::Point, CD_PROP_FLOAT, get_fn}; - fn(iter); - if (iter.is_stopped()) { - return false; - } - } - return true; - } - - void foreach_domain(const FunctionRef callback) const final - { - callback(AttrDomain::Point); - } -}; - -/** - * In this function all the attribute providers for a mesh component are created. Most data in this - * function is statically allocated, because it does not change over time. - */ -static GeometryAttributeProviders create_attribute_providers_for_mesh() -{ -#define MAKE_MUTABLE_CUSTOM_DATA_GETTER(NAME) \ - [](void *owner) -> CustomData * { \ - Mesh *mesh = static_cast(owner); \ - return &mesh->NAME; \ - } -#define MAKE_CONST_CUSTOM_DATA_GETTER(NAME) \ - [](const void *owner) -> const CustomData * { \ - const Mesh *mesh = static_cast(owner); \ - return &mesh->NAME; \ - } -#define MAKE_GET_ELEMENT_NUM_GETTER(NAME) \ - [](const void *owner) -> int { \ - const Mesh *mesh = static_cast(owner); \ - return mesh->NAME; \ - } - - static CustomDataAccessInfo corner_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(corner_data), - MAKE_CONST_CUSTOM_DATA_GETTER(corner_data), - MAKE_GET_ELEMENT_NUM_GETTER(corners_num)}; - static CustomDataAccessInfo point_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(vert_data), - MAKE_CONST_CUSTOM_DATA_GETTER(vert_data), - MAKE_GET_ELEMENT_NUM_GETTER(verts_num)}; - static CustomDataAccessInfo edge_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(edge_data), - MAKE_CONST_CUSTOM_DATA_GETTER(edge_data), - MAKE_GET_ELEMENT_NUM_GETTER(edges_num)}; - static CustomDataAccessInfo face_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(face_data), - MAKE_CONST_CUSTOM_DATA_GETTER(face_data), - MAKE_GET_ELEMENT_NUM_GETTER(faces_num)}; - -#undef MAKE_CONST_CUSTOM_DATA_GETTER -#undef MAKE_MUTABLE_CUSTOM_DATA_GETTER - - static BuiltinCustomDataLayerProvider position("position", - AttrDomain::Point, - CD_PROP_FLOAT3, - BuiltinAttributeProvider::NonDeletable, - point_access, - tag_component_positions_changed); - - static BuiltinCustomDataLayerProvider id("id", - AttrDomain::Point, - CD_PROP_INT32, - BuiltinAttributeProvider::Deletable, - point_access, - nullptr); - - static const auto material_index_clamp = mf::build::SI1_SO( - "Material Index Validate", - [](int value) { - /* Use #short for the maximum since many areas still use that type for indices. */ - return std::clamp(value, 0, std::numeric_limits::max()); - }, - mf::build::exec_presets::AllSpanOrSingle()); - static BuiltinCustomDataLayerProvider material_index("material_index", - AttrDomain::Face, - CD_PROP_INT32, - BuiltinAttributeProvider::Deletable, - face_access, - nullptr, - AttributeValidator{&material_index_clamp}); - - static const auto int2_index_clamp = mf::build::SI1_SO( - "Index Validate", - [](int2 value) { return math::max(value, int2(0)); }, - mf::build::exec_presets::AllSpanOrSingle()); - static BuiltinCustomDataLayerProvider edge_verts(".edge_verts", - AttrDomain::Edge, - CD_PROP_INT32_2D, - BuiltinAttributeProvider::NonDeletable, - edge_access, - nullptr, - AttributeValidator{&int2_index_clamp}); - - /* NOTE: This clamping is more of a last resort, since it's quite easy to make an - * invalid mesh that will crash Blender by arbitrarily editing this attribute. */ - static const auto int_index_clamp = mf::build::SI1_SO( - "Index Validate", - [](int value) { return std::max(value, 0); }, - mf::build::exec_presets::AllSpanOrSingle()); - static BuiltinCustomDataLayerProvider corner_vert(".corner_vert", - AttrDomain::Corner, - CD_PROP_INT32, - BuiltinAttributeProvider::NonDeletable, - corner_access, - nullptr, - AttributeValidator{&int_index_clamp}); - static BuiltinCustomDataLayerProvider corner_edge(".corner_edge", - AttrDomain::Corner, - CD_PROP_INT32, - BuiltinAttributeProvider::NonDeletable, - corner_access, - nullptr, - AttributeValidator{&int_index_clamp}); - - static BuiltinCustomDataLayerProvider sharp_face("sharp_face", - AttrDomain::Face, - CD_PROP_BOOL, - BuiltinAttributeProvider::Deletable, - face_access, - tag_component_sharpness_changed); - - static BuiltinCustomDataLayerProvider sharp_edge("sharp_edge", - AttrDomain::Edge, - CD_PROP_BOOL, - BuiltinAttributeProvider::Deletable, - edge_access, - tag_component_sharpness_changed); - - static MeshVertexGroupsAttributeProvider vertex_groups; - static CustomDataAttributeProvider corner_custom_data(AttrDomain::Corner, corner_access); - static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access); - static CustomDataAttributeProvider edge_custom_data(AttrDomain::Edge, edge_access); - static CustomDataAttributeProvider face_custom_data(AttrDomain::Face, face_access); - - return GeometryAttributeProviders({&position, - &edge_verts, - &corner_vert, - &corner_edge, - &id, - &material_index, - &sharp_face, - &sharp_edge}, - {&corner_custom_data, - &vertex_groups, - &point_custom_data, - &edge_custom_data, - &face_custom_data}); -} - -static AttributeAccessorFunctions get_mesh_accessor_functions() -{ - static const GeometryAttributeProviders providers = create_attribute_providers_for_mesh(); - AttributeAccessorFunctions fn = - attribute_accessor_functions::accessor_functions_for_providers(); - fn.domain_size = [](const void *owner, const AttrDomain domain) { - if (owner == nullptr) { - return 0; - } - const Mesh &mesh = *static_cast(owner); - switch (domain) { - case AttrDomain::Point: - return mesh.verts_num; - case AttrDomain::Edge: - return mesh.edges_num; - case AttrDomain::Face: - return mesh.faces_num; - case AttrDomain::Corner: - return mesh.corners_num; - default: - return 0; - } - }; - fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { - return ELEM(domain, AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face, AttrDomain::Corner); - }; - fn.adapt_domain = [](const void *owner, - const GVArray &varray, - const AttrDomain from_domain, - const AttrDomain to_domain) -> GVArray { - if (owner == nullptr) { - return {}; - } - const Mesh &mesh = *static_cast(owner); - return adapt_mesh_attribute_domain(mesh, varray, from_domain, to_domain); - }; - return fn; -} - -static const AttributeAccessorFunctions &get_mesh_accessor_functions_ref() -{ - static const AttributeAccessorFunctions fn = get_mesh_accessor_functions(); - return fn; -} - -} // namespace blender::bke - -blender::bke::AttributeAccessor Mesh::attributes() const -{ - return blender::bke::AttributeAccessor(this, blender::bke::get_mesh_accessor_functions_ref()); -} - -blender::bke::MutableAttributeAccessor Mesh::attributes_for_write() -{ - return blender::bke::MutableAttributeAccessor(this, - blender::bke::get_mesh_accessor_functions_ref()); -} - -namespace blender::bke { - std::optional MeshComponent::attributes() const { - return AttributeAccessor(mesh_, get_mesh_accessor_functions_ref()); + return AttributeAccessor(mesh_, mesh_attribute_accessor_functions()); } std::optional MeshComponent::attributes_for_write() { Mesh *mesh = this->get_for_write(); - return MutableAttributeAccessor(mesh, get_mesh_accessor_functions_ref()); + return MutableAttributeAccessor(mesh, mesh_attribute_accessor_functions()); } /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc index c933d90cf9e..b2e04e489da 100644 --- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc +++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc @@ -115,127 +115,23 @@ void PointCloudComponent::count_memory(MemoryCounter &memory) const /** \} */ +} // namespace blender::bke + +namespace blender::bke { + /* -------------------------------------------------------------------- */ /** \name Attribute Access * \{ */ -static void tag_component_positions_changed(void *owner) -{ - PointCloud &points = *static_cast(owner); - points.tag_positions_changed(); -} - -static void tag_component_radius_changed(void *owner) -{ - PointCloud &points = *static_cast(owner); - points.tag_radii_changed(); -} - -/** - * In this function all the attribute providers for a point cloud component are created. Most data - * in this function is statically allocated, because it does not change over time. - */ -static GeometryAttributeProviders create_attribute_providers_for_point_cloud() -{ - static CustomDataAccessInfo point_access = { - [](void *owner) -> CustomData * { - PointCloud *pointcloud = static_cast(owner); - return &pointcloud->pdata; - }, - [](const void *owner) -> const CustomData * { - const PointCloud *pointcloud = static_cast(owner); - return &pointcloud->pdata; - }, - [](const void *owner) -> int { - const PointCloud *pointcloud = static_cast(owner); - return pointcloud->totpoint; - }}; - - static BuiltinCustomDataLayerProvider position("position", - AttrDomain::Point, - CD_PROP_FLOAT3, - BuiltinAttributeProvider::NonDeletable, - point_access, - tag_component_positions_changed); - static BuiltinCustomDataLayerProvider radius("radius", - AttrDomain::Point, - CD_PROP_FLOAT, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_radius_changed); - static BuiltinCustomDataLayerProvider id("id", - AttrDomain::Point, - CD_PROP_INT32, - BuiltinAttributeProvider::Deletable, - point_access, - nullptr); - static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access); - return GeometryAttributeProviders({&position, &radius, &id}, {&point_custom_data}); -} - -static AttributeAccessorFunctions get_pointcloud_accessor_functions() -{ - static const GeometryAttributeProviders providers = create_attribute_providers_for_point_cloud(); - AttributeAccessorFunctions fn = - attribute_accessor_functions::accessor_functions_for_providers(); - fn.domain_size = [](const void *owner, const AttrDomain domain) { - if (owner == nullptr) { - return 0; - } - const PointCloud &pointcloud = *static_cast(owner); - switch (domain) { - case AttrDomain::Point: - return pointcloud.totpoint; - default: - return 0; - } - }; - fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { - return domain == AttrDomain::Point; - }; - fn.adapt_domain = [](const void * /*owner*/, - const GVArray &varray, - const AttrDomain from_domain, - const AttrDomain to_domain) { - if (from_domain == to_domain && from_domain == AttrDomain::Point) { - return varray; - } - return GVArray{}; - }; - return fn; -} - -static const AttributeAccessorFunctions &get_pointcloud_accessor_functions_ref() -{ - static const AttributeAccessorFunctions fn = get_pointcloud_accessor_functions(); - return fn; -} - -} // namespace blender::bke - -blender::bke::AttributeAccessor PointCloud::attributes() const -{ - return blender::bke::AttributeAccessor(this, - blender::bke::get_pointcloud_accessor_functions_ref()); -} - -blender::bke::MutableAttributeAccessor PointCloud::attributes_for_write() -{ - return blender::bke::MutableAttributeAccessor( - this, blender::bke::get_pointcloud_accessor_functions_ref()); -} - -namespace blender::bke { - std::optional PointCloudComponent::attributes() const { - return AttributeAccessor(pointcloud_, get_pointcloud_accessor_functions_ref()); + return AttributeAccessor(pointcloud_, pointcloud_attribute_accessor_functions()); } std::optional PointCloudComponent::attributes_for_write() { PointCloud *pointcloud = this->get_for_write(); - return MutableAttributeAccessor(pointcloud, get_pointcloud_accessor_functions_ref()); + return MutableAttributeAccessor(pointcloud, pointcloud_attribute_accessor_functions()); } /** \} */ diff --git a/source/blender/blenkernel/intern/grease_pencil.cc b/source/blender/blenkernel/intern/grease_pencil.cc index c6ba2eeba35..354b8ae352b 100644 --- a/source/blender/blenkernel/intern/grease_pencil.cc +++ b/source/blender/blenkernel/intern/grease_pencil.cc @@ -3941,6 +3941,18 @@ void GreasePencil::print_layer_tree() this->root_group().print_nodes("Layer Tree:"); } +blender::bke::AttributeAccessor GreasePencil::attributes() const +{ + return blender::bke::AttributeAccessor( + this, blender::bke::greasepencil::get_attribute_accessor_functions()); +} + +blender::bke::MutableAttributeAccessor GreasePencil::attributes_for_write() +{ + return blender::bke::MutableAttributeAccessor( + this, blender::bke::greasepencil::get_attribute_accessor_functions()); +} + /** \} */ /* ------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/grease_pencil_attributes.cc b/source/blender/blenkernel/intern/grease_pencil_attributes.cc new file mode 100644 index 00000000000..39b0d181a6f --- /dev/null +++ b/source/blender/blenkernel/intern/grease_pencil_attributes.cc @@ -0,0 +1,85 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_grease_pencil.hh" + +#include "DNA_grease_pencil_types.h" + +#include "attribute_access_intern.hh" + +namespace blender ::bke::greasepencil { + +static GeometryAttributeProviders create_attribute_providers_for_grease_pencil() +{ + static CustomDataAccessInfo layers_access = { + [](void *owner) -> CustomData * { + GreasePencil &grease_pencil = *static_cast(owner); + return &grease_pencil.layers_data; + }, + [](const void *owner) -> const CustomData * { + const GreasePencil &grease_pencil = *static_cast(owner); + return &grease_pencil.layers_data; + }, + [](const void *owner) -> int { + const GreasePencil &grease_pencil = *static_cast(owner); + return grease_pencil.layers().size(); + }}; + + static CustomDataAttributeProvider layer_custom_data(AttrDomain::Layer, layers_access); + + return GeometryAttributeProviders({}, {&layer_custom_data}); +} + +static GVArray adapt_grease_pencil_attribute_domain(const GreasePencil & /*grease_pencil*/, + const GVArray &varray, + const AttrDomain from, + const AttrDomain to) +{ + if (from == to) { + return varray; + } + return {}; +} + +static AttributeAccessorFunctions get_grease_pencil_accessor_functions() +{ + static const GeometryAttributeProviders providers = + create_attribute_providers_for_grease_pencil(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const AttrDomain domain) { + if (owner == nullptr) { + return 0; + } + const GreasePencil &grease_pencil = *static_cast(owner); + switch (domain) { + case AttrDomain::Layer: + return int(grease_pencil.layers().size()); + default: + return 0; + } + }; + fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { + return domain == AttrDomain::Layer; + }; + fn.adapt_domain = [](const void *owner, + const GVArray &varray, + const AttrDomain from_domain, + const AttrDomain to_domain) -> GVArray { + if (owner == nullptr) { + return {}; + } + const GreasePencil &grease_pencil = *static_cast(owner); + return adapt_grease_pencil_attribute_domain(grease_pencil, varray, from_domain, to_domain); + }; + return fn; +} + +const AttributeAccessorFunctions &get_attribute_accessor_functions() +{ + static const AttributeAccessorFunctions fn = get_grease_pencil_accessor_functions(); + return fn; +} + +} // namespace blender::bke::greasepencil diff --git a/source/blender/blenkernel/intern/instances.cc b/source/blender/blenkernel/intern/instances.cc index da1f3910da0..9d3694ff639 100644 --- a/source/blender/blenkernel/intern/instances.cc +++ b/source/blender/blenkernel/intern/instances.cc @@ -62,6 +62,16 @@ void InstanceReference::count_memory(MemoryCounter &memory) const } } +AttributeAccessor Instances::attributes() const +{ + return AttributeAccessor(this, instance_attribute_accessor_functions()); +} + +MutableAttributeAccessor Instances::attributes_for_write() +{ + return MutableAttributeAccessor(this, instance_attribute_accessor_functions()); +} + static void convert_collection_to_instances(const Collection &collection, bke::Instances &instances) { diff --git a/source/blender/blenkernel/intern/instances_attributes.cc b/source/blender/blenkernel/intern/instances_attributes.cc new file mode 100644 index 00000000000..4d67ddec8ad --- /dev/null +++ b/source/blender/blenkernel/intern/instances_attributes.cc @@ -0,0 +1,108 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_math_matrix_types.hh" + +#include "BKE_instances.hh" + +#include "attribute_access_intern.hh" + +namespace blender::bke { + +static void tag_component_reference_index_changed(void *owner) +{ + Instances &instances = *static_cast(owner); + instances.tag_reference_handles_changed(); +} + +static GeometryAttributeProviders create_attribute_providers_for_instances() +{ + static CustomDataAccessInfo instance_custom_data_access = { + [](void *owner) -> CustomData * { + Instances *instances = static_cast(owner); + return &instances->custom_data_attributes(); + }, + [](const void *owner) -> const CustomData * { + const Instances *instances = static_cast(owner); + return &instances->custom_data_attributes(); + }, + [](const void *owner) -> int { + const Instances *instances = static_cast(owner); + return instances->instances_num(); + }}; + + /** + * IDs of the instances. They are used for consistency over multiple frames for things like + * motion blur. Proper stable ID data that actually helps when rendering can only be generated + * in some situations, so this vector is allowed to be empty, in which case the index of each + * instance will be used for the final ID. + */ + static BuiltinCustomDataLayerProvider id("id", + AttrDomain::Instance, + CD_PROP_INT32, + BuiltinAttributeProvider::Deletable, + instance_custom_data_access, + nullptr); + + static BuiltinCustomDataLayerProvider instance_transform("instance_transform", + AttrDomain::Instance, + CD_PROP_FLOAT4X4, + BuiltinAttributeProvider::NonDeletable, + instance_custom_data_access, + nullptr); + + /** Indices into `Instances::references_`. Determines what data is instanced. */ + static BuiltinCustomDataLayerProvider reference_index(".reference_index", + AttrDomain::Instance, + CD_PROP_INT32, + BuiltinAttributeProvider::NonDeletable, + instance_custom_data_access, + tag_component_reference_index_changed); + + static CustomDataAttributeProvider instance_custom_data(AttrDomain::Instance, + instance_custom_data_access); + + return GeometryAttributeProviders({&instance_transform, &id, &reference_index}, + {&instance_custom_data}); +} + +static AttributeAccessorFunctions get_instances_accessor_functions() +{ + static const GeometryAttributeProviders providers = create_attribute_providers_for_instances(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const AttrDomain domain) { + if (owner == nullptr) { + return 0; + } + const Instances *instances = static_cast(owner); + switch (domain) { + case AttrDomain::Instance: + return instances->instances_num(); + default: + return 0; + } + }; + fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { + return domain == AttrDomain::Instance; + }; + fn.adapt_domain = [](const void * /*owner*/, + const GVArray &varray, + const AttrDomain from_domain, + const AttrDomain to_domain) { + if (from_domain == to_domain && from_domain == AttrDomain::Instance) { + return varray; + } + return GVArray{}; + }; + return fn; +} + +const AttributeAccessorFunctions &instance_attribute_accessor_functions() +{ + static const AttributeAccessorFunctions fn = get_instances_accessor_functions(); + return fn; +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index b30181c28a1..8d34b133394 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -701,6 +701,17 @@ void Mesh::count_memory(blender::MemoryCounter &memory) const CustomData_count_memory(this->corner_data, this->corners_num, memory); } +blender::bke::AttributeAccessor Mesh::attributes() const +{ + return blender::bke::AttributeAccessor(this, blender::bke::mesh_attribute_accessor_functions()); +} + +blender::bke::MutableAttributeAccessor Mesh::attributes_for_write() +{ + return blender::bke::MutableAttributeAccessor(this, + blender::bke::mesh_attribute_accessor_functions()); +} + Mesh *BKE_mesh_new_nomain(const int verts_num, const int edges_num, const int faces_num, diff --git a/source/blender/blenkernel/intern/mesh_attributes.cc b/source/blender/blenkernel/intern/mesh_attributes.cc new file mode 100644 index 00000000000..b9e3d42ba51 --- /dev/null +++ b/source/blender/blenkernel/intern/mesh_attributes.cc @@ -0,0 +1,1015 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute_math.hh" +#include "BKE_deform.hh" +#include "BKE_mesh.hh" + +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BLI_listbase.h" + +#include "FN_multi_function_builder.hh" + +#include "attribute_access_intern.hh" + +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.verts_num); + const Span corner_verts = mesh.corner_verts(); + + attribute_math::DefaultMixer mixer(r_values); + for (const int corner : IndexRange(mesh.corners_num)) { + mixer.mix_in(corner_verts[corner], old_values[corner]); + } + 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.verts_num); + const Span corner_verts = mesh.corner_verts(); + + r_values.fill(true); + for (const int corner : IndexRange(mesh.corners_num)) { + const int point_index = corner_verts[corner]; + + if (!old_values[corner]) { + r_values[point_index] = false; + } + } + + /* Deselect loose vertices without corners that are still selected from the 'true' default. */ + const LooseVertCache &loose_verts = mesh.verts_no_face(); + if (loose_verts.count > 0) { + const BitSpan bits = loose_verts.is_loose_bits; + threading::parallel_for(bits.index_range(), 2048, [&](const IndexRange range) { + for (const int vert_index : range) { + if (bits[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.verts_num); + 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 corner_verts = mesh.corner_verts(); + + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { + using T = decltype(dummy); + new_varray = VArray::ForFunc( + mesh.corners_num, [corner_verts, varray = varray.typed()](const int64_t corner) { + return varray[corner_verts[corner]]; + }); + }); + return new_varray; +} + +static GVArray adapt_mesh_domain_corner_to_face(const Mesh &mesh, const GVArray &varray) +{ + const OffsetIndices faces = mesh.faces(); + + 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( + faces.size(), [faces, varray = varray.typed()](const int face_index) { + /* A face is selected if all of its corners were selected. */ + for (const int loop_index : faces[face_index]) { + if (!varray[loop_index]) { + return false; + } + } + return true; + }); + } + else { + new_varray = VArray::ForFunc( + faces.size(), [faces, varray = varray.typed()](const int face_index) { + T return_value; + attribute_math::DefaultMixer mixer({&return_value, 1}); + for (const int loop_index : faces[face_index]) { + 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.edges_num); + const OffsetIndices faces = mesh.faces(); + const Span corner_edges = mesh.corner_edges(); + + attribute_math::DefaultMixer mixer(r_values); + + for (const int face_index : faces.index_range()) { + const IndexRange face = faces[face_index]; + + /* For every edge, mix values from the two adjacent corners (the current and next corner). */ + for (const int corner : face) { + const int next_corner = mesh::face_corner_next(face, corner); + const int edge_index = corner_edges[corner]; + mixer.mix_in(edge_index, old_values[corner]); + mixer.mix_in(edge_index, old_values[next_corner]); + } + } + + 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.edges_num); + const OffsetIndices faces = mesh.faces(); + const Span corner_edges = mesh.corner_edges(); + + r_values.fill(true); + for (const int face_index : faces.index_range()) { + const IndexRange face = faces[face_index]; + + for (const int corner : face) { + const int next_corner = mesh::face_corner_next(face, corner); + const int edge_index = corner_edges[corner]; + if (!old_values[corner] || !old_values[next_corner]) { + r_values[edge_index] = false; + } + } + } + + const LooseEdgeCache &loose_edges = mesh.loose_edges(); + if (loose_edges.count > 0) { + /* Deselect loose edges without corners that are still selected from the 'true' default. */ + threading::parallel_for(IndexRange(mesh.edges_num), 2048, [&](const IndexRange range) { + for (const int edge_index : range) { + if (loose_edges.is_loose_bits[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.edges_num); + 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.verts_num); + const OffsetIndices faces = mesh.faces(); + const Span corner_verts = mesh.corner_verts(); + + attribute_math::DefaultMixer mixer(r_values); + + for (const int face_index : faces.index_range()) { + const T value = old_values[face_index]; + for (const int vert : corner_verts.slice(faces[face_index])) { + mixer.mix_in(vert, 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.verts_num); + const OffsetIndices faces = mesh.faces(); + const Span corner_verts = mesh.corner_verts(); + + r_values.fill(false); + threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) { + for (const int face_index : range) { + if (old_values[face_index]) { + for (const int vert : corner_verts.slice(faces[face_index])) { + r_values[vert] = true; + } + } + } + }); +} + +static GVArray adapt_mesh_domain_face_to_point(const Mesh &mesh, const GVArray &varray) +{ + GArray<> values(varray.type(), mesh.verts_num); + 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.corners_num); + const OffsetIndices faces = mesh.faces(); + + threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) { + for (const int face_index : range) { + MutableSpan face_corner_values = r_values.slice(faces[face_index]); + face_corner_values.fill(old_values[face_index]); + } + }); +} + +static GVArray adapt_mesh_domain_face_to_corner(const Mesh &mesh, const GVArray &varray) +{ + GArray<> values(varray.type(), mesh.corners_num); + 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.edges_num); + const OffsetIndices faces = mesh.faces(); + const Span corner_edges = mesh.corner_edges(); + + attribute_math::DefaultMixer mixer(r_values); + + for (const int face_index : faces.index_range()) { + const T value = old_values[face_index]; + for (const int edge : corner_edges.slice(faces[face_index])) { + mixer.mix_in(edge, 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.edges_num); + const OffsetIndices faces = mesh.faces(); + const Span corner_edges = mesh.corner_edges(); + + r_values.fill(false); + threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) { + for (const int face_index : range) { + if (old_values[face_index]) { + for (const int edge : corner_edges.slice(faces[face_index])) { + r_values[edge] = true; + } + } + } + }); +} + +static GVArray adapt_mesh_domain_face_to_edge(const Mesh &mesh, const GVArray &varray) +{ + GArray<> values(varray.type(), mesh.edges_num); + 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 OffsetIndices faces = mesh.faces(); + const Span corner_verts = mesh.corner_verts(); + + 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.faces_num, + [corner_verts, faces, varray = varray.typed()](const int face_index) { + /* A face is selected if all of its vertices were selected. */ + for (const int vert : corner_verts.slice(faces[face_index])) { + if (!varray[vert]) { + return false; + } + } + return true; + }); + } + else { + new_varray = VArray::ForFunc( + mesh.faces_num, + [corner_verts, faces, varray = varray.typed()](const int face_index) { + T return_value; + attribute_math::DefaultMixer mixer({&return_value, 1}); + for (const int vert : corner_verts.slice(faces[face_index])) { + mixer.mix_in(0, varray[vert]); + } + 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 int2 &edge = edges[edge_index]; + return varray[edge[0]] && varray[edge[1]]; + }); + } + 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 int2 &edge = edges[edge_index]; + mixer.mix_in(0, varray[edge[0]]); + mixer.mix_in(0, varray[edge[1]]); + 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.corners_num); + const OffsetIndices faces = mesh.faces(); + const Span corner_edges = mesh.corner_edges(); + + attribute_math::DefaultMixer mixer(r_values); + + for (const int face_index : faces.index_range()) { + const IndexRange face = faces[face_index]; + + /* For every corner, mix the values from the adjacent edges on the face. */ + for (const int loop_index : face) { + const int loop_index_prev = mesh::face_corner_prev(face, loop_index); + const int edge = corner_edges[loop_index]; + const int edge_prev = corner_edges[loop_index_prev]; + mixer.mix_in(loop_index, old_values[edge]); + mixer.mix_in(loop_index, old_values[edge_prev]); + } + } + + 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.corners_num); + const OffsetIndices faces = mesh.faces(); + const Span corner_edges = mesh.corner_edges(); + + r_values.fill(false); + + threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) { + for (const int face_index : range) { + const IndexRange face = faces[face_index]; + for (const int loop_index : face) { + const int loop_index_prev = mesh::face_corner_prev(face, loop_index); + const int edge = corner_edges[loop_index]; + const int edge_prev = corner_edges[loop_index_prev]; + if (old_values[edge] && old_values[edge_prev]) { + r_values[loop_index] = true; + } + } + } + }); +} + +static GVArray adapt_mesh_domain_edge_to_corner(const Mesh &mesh, const GVArray &varray) +{ + GArray<> values(varray.type(), mesh.corners_num); + 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.verts_num); + const Span edges = mesh.edges(); + + attribute_math::DefaultMixer mixer(r_values); + + for (const int edge_index : IndexRange(mesh.edges_num)) { + const int2 &edge = edges[edge_index]; + const T value = old_values[edge_index]; + mixer.mix_in(edge[0], value); + mixer.mix_in(edge[1], 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.verts_num); + const Span edges = mesh.edges(); + + /* Multiple threads can write to the same index here, but they are only + * writing true, and writing to single bytes is expected to be threadsafe. */ + r_values.fill(false); + threading::parallel_for(edges.index_range(), 4096, [&](const IndexRange range) { + for (const int edge_index : range) { + if (old_values[edge_index]) { + const int2 &edge = edges[edge_index]; + r_values[edge[0]] = true; + r_values[edge[1]] = true; + } + } + }); +} + +static GVArray adapt_mesh_domain_edge_to_point(const Mesh &mesh, const GVArray &varray) +{ + GArray<> values(varray.type(), mesh.verts_num); + 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 OffsetIndices faces = mesh.faces(); + const Span corner_edges = mesh.corner_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) { + /* A face is selected if all of its edges are selected. */ + new_varray = VArray::ForFunc( + faces.size(), [corner_edges, faces, varray = varray.typed()](const int face_index) { + for (const int edge : corner_edges.slice(faces[face_index])) { + if (!varray[edge]) { + return false; + } + } + return true; + }); + } + else { + new_varray = VArray::ForFunc( + faces.size(), [corner_edges, faces, varray = varray.typed()](const int face_index) { + T return_value; + attribute_math::DefaultMixer mixer({&return_value, 1}); + for (const int edge : corner_edges.slice(faces[face_index])) { + mixer.mix_in(0, varray[edge]); + } + mixer.finalize(); + return return_value; + }); + } + } + }); + return new_varray; +} + +static bool can_simple_adapt_for_single(const Mesh &mesh, + const AttrDomain from_domain, + const AttrDomain 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 AttrDomain::Point: + /* All other domains are always connected to points. */ + return true; + case AttrDomain::Edge: + if (to_domain == AttrDomain::Point) { + return mesh.loose_verts().count == 0; + } + return true; + case AttrDomain::Face: + if (to_domain == AttrDomain::Point) { + return mesh.verts_no_face().count == 0; + } + if (to_domain == AttrDomain::Edge) { + return mesh.loose_edges().count == 0; + } + return true; + case AttrDomain::Corner: + if (to_domain == AttrDomain::Point) { + return mesh.verts_no_face().count == 0; + } + if (to_domain == AttrDomain::Edge) { + return mesh.loose_edges().count == 0; + } + return true; + default: + BLI_assert_unreachable(); + return false; + } +} + +static GVArray adapt_mesh_attribute_domain(const Mesh &mesh, + const GVArray &varray, + const AttrDomain from_domain, + const AttrDomain to_domain) +{ + if (!varray) { + return {}; + } + if (varray.is_empty()) { + return {}; + } + if (from_domain == to_domain) { + return varray; + } + if (varray.is_single()) { + if (can_simple_adapt_for_single(mesh, from_domain, to_domain)) { + BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), value); + varray.get_internal_single(value); + return GVArray::ForSingle(varray.type(), mesh.attributes().domain_size(to_domain), value); + } + } + + switch (from_domain) { + case AttrDomain::Corner: { + switch (to_domain) { + case AttrDomain::Point: + return adapt_mesh_domain_corner_to_point(mesh, varray); + case AttrDomain::Face: + return adapt_mesh_domain_corner_to_face(mesh, varray); + case AttrDomain::Edge: + return adapt_mesh_domain_corner_to_edge(mesh, varray); + default: + break; + } + break; + } + case AttrDomain::Point: { + switch (to_domain) { + case AttrDomain::Corner: + return adapt_mesh_domain_point_to_corner(mesh, varray); + case AttrDomain::Face: + return adapt_mesh_domain_point_to_face(mesh, varray); + case AttrDomain::Edge: + return adapt_mesh_domain_point_to_edge(mesh, varray); + default: + break; + } + break; + } + case AttrDomain::Face: { + switch (to_domain) { + case AttrDomain::Point: + return adapt_mesh_domain_face_to_point(mesh, varray); + case AttrDomain::Corner: + return adapt_mesh_domain_face_to_corner(mesh, varray); + case AttrDomain::Edge: + return adapt_mesh_domain_face_to_edge(mesh, varray); + default: + break; + } + break; + } + case AttrDomain::Edge: { + switch (to_domain) { + case AttrDomain::Corner: + return adapt_mesh_domain_edge_to_corner(mesh, varray); + case AttrDomain::Point: + return adapt_mesh_domain_edge_to_point(mesh, varray); + case AttrDomain::Face: + return adapt_mesh_domain_edge_to_face(mesh, varray); + default: + break; + } + break; + } + default: + break; + } + + return {}; +} + +static void tag_component_positions_changed(void *owner) +{ + Mesh *mesh = static_cast(owner); + if (mesh != nullptr) { + mesh->tag_positions_changed(); + } +} + +static void tag_component_sharpness_changed(void *owner) +{ + if (Mesh *mesh = static_cast(owner)) { + mesh->tag_sharpness_changed(); + } +} + +/** + * This provider makes vertex groups available as float attributes. + */ +class MeshVertexGroupsAttributeProvider final : public DynamicAttributesProvider { + public: + GAttributeReader try_get_for_read(const void *owner, const StringRef attribute_id) const final + { + if (bke::attribute_name_is_anonymous(attribute_id)) { + return {}; + } + const Mesh *mesh = static_cast(owner); + if (mesh == nullptr) { + return {}; + } + const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names, + attribute_id); + if (vertex_group_index < 0) { + return {}; + } + const Span dverts = mesh->deform_verts(); + return this->get_for_vertex_group_index(*mesh, dverts, vertex_group_index); + } + + GAttributeReader get_for_vertex_group_index(const Mesh &mesh, + const Span dverts, + const int vertex_group_index) const + { + BLI_assert(vertex_group_index >= 0); + if (dverts.is_empty()) { + return {VArray::ForSingle(0.0f, mesh.verts_num), AttrDomain::Point}; + } + return {varray_for_deform_verts(dverts, vertex_group_index), AttrDomain::Point}; + } + + GAttributeWriter try_get_for_write(void *owner, const StringRef attribute_id) const final + { + if (bke::attribute_name_is_anonymous(attribute_id)) { + return {}; + } + Mesh *mesh = static_cast(owner); + if (mesh == nullptr) { + return {}; + } + + const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names, + attribute_id); + if (vertex_group_index < 0) { + return {}; + } + MutableSpan dverts = mesh->deform_verts_for_write(); + return {varray_for_mutable_deform_verts(dverts, vertex_group_index), AttrDomain::Point}; + } + + bool try_delete(void *owner, const StringRef attribute_id) const final + { + if (bke::attribute_name_is_anonymous(attribute_id)) { + return false; + } + Mesh *mesh = static_cast(owner); + if (mesh == nullptr) { + return true; + } + + const std::string name = attribute_id; + + int index; + bDeformGroup *group; + if (!BKE_id_defgroup_name_find(&mesh->id, name.c_str(), &index, &group)) { + return false; + } + BLI_remlink(&mesh->vertex_group_names, group); + MEM_freeN(group); + if (mesh->deform_verts().is_empty()) { + return true; + } + + MutableSpan dverts = mesh->deform_verts_for_write(); + remove_defgroup_index(dverts, index); + return true; + } + + bool foreach_attribute(const void *owner, + const FunctionRef fn) const final + { + const Mesh *mesh = static_cast(owner); + if (mesh == nullptr) { + return true; + } + + const Span dverts = mesh->deform_verts(); + + int group_index = 0; + LISTBASE_FOREACH_INDEX (const bDeformGroup *, group, &mesh->vertex_group_names, group_index) { + const auto get_fn = [&]() { + return this->get_for_vertex_group_index(*mesh, dverts, group_index); + }; + + AttributeIter iter{group->name, AttrDomain::Point, CD_PROP_FLOAT, get_fn}; + fn(iter); + if (iter.is_stopped()) { + return false; + } + } + return true; + } + + void foreach_domain(const FunctionRef callback) const final + { + callback(AttrDomain::Point); + } +}; + +/** + * In this function all the attribute providers for a mesh component are created. Most data in this + * function is statically allocated, because it does not change over time. + */ +static GeometryAttributeProviders create_attribute_providers_for_mesh() +{ +#define MAKE_MUTABLE_CUSTOM_DATA_GETTER(NAME) \ + [](void *owner) -> CustomData * { \ + Mesh *mesh = static_cast(owner); \ + return &mesh->NAME; \ + } +#define MAKE_CONST_CUSTOM_DATA_GETTER(NAME) \ + [](const void *owner) -> const CustomData * { \ + const Mesh *mesh = static_cast(owner); \ + return &mesh->NAME; \ + } +#define MAKE_GET_ELEMENT_NUM_GETTER(NAME) \ + [](const void *owner) -> int { \ + const Mesh *mesh = static_cast(owner); \ + return mesh->NAME; \ + } + + static CustomDataAccessInfo corner_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(corner_data), + MAKE_CONST_CUSTOM_DATA_GETTER(corner_data), + MAKE_GET_ELEMENT_NUM_GETTER(corners_num)}; + static CustomDataAccessInfo point_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(vert_data), + MAKE_CONST_CUSTOM_DATA_GETTER(vert_data), + MAKE_GET_ELEMENT_NUM_GETTER(verts_num)}; + static CustomDataAccessInfo edge_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(edge_data), + MAKE_CONST_CUSTOM_DATA_GETTER(edge_data), + MAKE_GET_ELEMENT_NUM_GETTER(edges_num)}; + static CustomDataAccessInfo face_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(face_data), + MAKE_CONST_CUSTOM_DATA_GETTER(face_data), + MAKE_GET_ELEMENT_NUM_GETTER(faces_num)}; + +#undef MAKE_CONST_CUSTOM_DATA_GETTER +#undef MAKE_MUTABLE_CUSTOM_DATA_GETTER + + static BuiltinCustomDataLayerProvider position("position", + AttrDomain::Point, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::NonDeletable, + point_access, + tag_component_positions_changed); + + static BuiltinCustomDataLayerProvider id("id", + AttrDomain::Point, + CD_PROP_INT32, + BuiltinAttributeProvider::Deletable, + point_access, + nullptr); + + static const auto material_index_clamp = mf::build::SI1_SO( + "Material Index Validate", + [](int value) { + /* Use #short for the maximum since many areas still use that type for indices. */ + return std::clamp(value, 0, std::numeric_limits::max()); + }, + mf::build::exec_presets::AllSpanOrSingle()); + static BuiltinCustomDataLayerProvider material_index("material_index", + AttrDomain::Face, + CD_PROP_INT32, + BuiltinAttributeProvider::Deletable, + face_access, + nullptr, + AttributeValidator{&material_index_clamp}); + + static const auto int2_index_clamp = mf::build::SI1_SO( + "Index Validate", + [](int2 value) { return math::max(value, int2(0)); }, + mf::build::exec_presets::AllSpanOrSingle()); + static BuiltinCustomDataLayerProvider edge_verts(".edge_verts", + AttrDomain::Edge, + CD_PROP_INT32_2D, + BuiltinAttributeProvider::NonDeletable, + edge_access, + nullptr, + AttributeValidator{&int2_index_clamp}); + + /* NOTE: This clamping is more of a last resort, since it's quite easy to make an + * invalid mesh that will crash Blender by arbitrarily editing this attribute. */ + static const auto int_index_clamp = mf::build::SI1_SO( + "Index Validate", + [](int value) { return std::max(value, 0); }, + mf::build::exec_presets::AllSpanOrSingle()); + static BuiltinCustomDataLayerProvider corner_vert(".corner_vert", + AttrDomain::Corner, + CD_PROP_INT32, + BuiltinAttributeProvider::NonDeletable, + corner_access, + nullptr, + AttributeValidator{&int_index_clamp}); + static BuiltinCustomDataLayerProvider corner_edge(".corner_edge", + AttrDomain::Corner, + CD_PROP_INT32, + BuiltinAttributeProvider::NonDeletable, + corner_access, + nullptr, + AttributeValidator{&int_index_clamp}); + + static BuiltinCustomDataLayerProvider sharp_face("sharp_face", + AttrDomain::Face, + CD_PROP_BOOL, + BuiltinAttributeProvider::Deletable, + face_access, + tag_component_sharpness_changed); + + static BuiltinCustomDataLayerProvider sharp_edge("sharp_edge", + AttrDomain::Edge, + CD_PROP_BOOL, + BuiltinAttributeProvider::Deletable, + edge_access, + tag_component_sharpness_changed); + + static MeshVertexGroupsAttributeProvider vertex_groups; + static CustomDataAttributeProvider corner_custom_data(AttrDomain::Corner, corner_access); + static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access); + static CustomDataAttributeProvider edge_custom_data(AttrDomain::Edge, edge_access); + static CustomDataAttributeProvider face_custom_data(AttrDomain::Face, face_access); + + return GeometryAttributeProviders({&position, + &edge_verts, + &corner_vert, + &corner_edge, + &id, + &material_index, + &sharp_face, + &sharp_edge}, + {&corner_custom_data, + &vertex_groups, + &point_custom_data, + &edge_custom_data, + &face_custom_data}); +} + +static AttributeAccessorFunctions get_mesh_accessor_functions() +{ + static const GeometryAttributeProviders providers = create_attribute_providers_for_mesh(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const AttrDomain domain) { + if (owner == nullptr) { + return 0; + } + const Mesh &mesh = *static_cast(owner); + switch (domain) { + case AttrDomain::Point: + return mesh.verts_num; + case AttrDomain::Edge: + return mesh.edges_num; + case AttrDomain::Face: + return mesh.faces_num; + case AttrDomain::Corner: + return mesh.corners_num; + default: + return 0; + } + }; + fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { + return ELEM(domain, AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face, AttrDomain::Corner); + }; + fn.adapt_domain = [](const void *owner, + const GVArray &varray, + const AttrDomain from_domain, + const AttrDomain to_domain) -> GVArray { + if (owner == nullptr) { + return {}; + } + const Mesh &mesh = *static_cast(owner); + return adapt_mesh_attribute_domain(mesh, varray, from_domain, to_domain); + }; + return fn; +} + +const AttributeAccessorFunctions &mesh_attribute_accessor_functions() +{ + static const AttributeAccessorFunctions fn = get_mesh_accessor_functions(); + return fn; +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index cb4c6b51bfe..86ae17219e5 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -284,6 +284,18 @@ void PointCloud::count_memory(blender::MemoryCounter &memory) const CustomData_count_memory(this->pdata, this->totpoint, memory); } +blender::bke::AttributeAccessor PointCloud::attributes() const +{ + return blender::bke::AttributeAccessor(this, + blender::bke::pointcloud_attribute_accessor_functions()); +} + +blender::bke::MutableAttributeAccessor PointCloud::attributes_for_write() +{ + return blender::bke::MutableAttributeAccessor( + this, blender::bke::pointcloud_attribute_accessor_functions()); +} + bool BKE_pointcloud_attribute_required(const PointCloud * /*pointcloud*/, const char *name) { return STREQ(name, POINTCLOUD_ATTR_POSITION); diff --git a/source/blender/blenkernel/intern/pointcloud_attributes.cc b/source/blender/blenkernel/intern/pointcloud_attributes.cc new file mode 100644 index 00000000000..ab44af45119 --- /dev/null +++ b/source/blender/blenkernel/intern/pointcloud_attributes.cc @@ -0,0 +1,105 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "DNA_pointcloud_types.h" + +#include "BKE_pointcloud.hh" + +#include "attribute_access_intern.hh" + +namespace blender::bke { + +static void tag_component_positions_changed(void *owner) +{ + PointCloud &points = *static_cast(owner); + points.tag_positions_changed(); +} + +static void tag_component_radius_changed(void *owner) +{ + PointCloud &points = *static_cast(owner); + points.tag_radii_changed(); +} + +/** + * In this function all the attribute providers for a point cloud component are created. Most data + * in this function is statically allocated, because it does not change over time. + */ +static GeometryAttributeProviders create_attribute_providers_for_point_cloud() +{ + static CustomDataAccessInfo point_access = { + [](void *owner) -> CustomData * { + PointCloud *pointcloud = static_cast(owner); + return &pointcloud->pdata; + }, + [](const void *owner) -> const CustomData * { + const PointCloud *pointcloud = static_cast(owner); + return &pointcloud->pdata; + }, + [](const void *owner) -> int { + const PointCloud *pointcloud = static_cast(owner); + return pointcloud->totpoint; + }}; + + static BuiltinCustomDataLayerProvider position("position", + AttrDomain::Point, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::NonDeletable, + point_access, + tag_component_positions_changed); + static BuiltinCustomDataLayerProvider radius("radius", + AttrDomain::Point, + CD_PROP_FLOAT, + BuiltinAttributeProvider::Deletable, + point_access, + tag_component_radius_changed); + static BuiltinCustomDataLayerProvider id("id", + AttrDomain::Point, + CD_PROP_INT32, + BuiltinAttributeProvider::Deletable, + point_access, + nullptr); + static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access); + return GeometryAttributeProviders({&position, &radius, &id}, {&point_custom_data}); +} + +static AttributeAccessorFunctions get_pointcloud_accessor_functions() +{ + static const GeometryAttributeProviders providers = create_attribute_providers_for_point_cloud(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const AttrDomain domain) { + if (owner == nullptr) { + return 0; + } + const PointCloud &pointcloud = *static_cast(owner); + switch (domain) { + case AttrDomain::Point: + return pointcloud.totpoint; + default: + return 0; + } + }; + fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { + return domain == AttrDomain::Point; + }; + fn.adapt_domain = [](const void * /*owner*/, + const GVArray &varray, + const AttrDomain from_domain, + const AttrDomain to_domain) { + if (from_domain == to_domain && from_domain == AttrDomain::Point) { + return varray; + } + return GVArray{}; + }; + return fn; +} + +const AttributeAccessorFunctions &pointcloud_attribute_accessor_functions() +{ + static const AttributeAccessorFunctions fn = get_pointcloud_accessor_functions(); + return fn; +} + +} // namespace blender::bke