From d68c47ff347bbb3824c27387f3387bd0583158a2 Mon Sep 17 00:00:00 2001 From: Iliya Katueshenock Date: Wed, 7 Dec 2022 18:22:44 +0100 Subject: [PATCH] Geometry Nodes: new Blur Attribute node The Blur Attribute node mixes values of neighboring elements in meshes and curves. Currently it supports points, edges and faces on meshes and points on curves. In theory, support for face corners could be added, but useful semantics are not obvious yet. The node calculates a weighted average of each element with its neighbors (based on curve/mesh topology). The weight of the element itself is always 1, and the weight of the neighbor elements is controlled by the weight input socket. In the future, more options for how different elements are weight can be added (e.g. smoothing groups and selection). The node can perform multiple blurring iterations to achieve a blurrier result. Generally, it is better to do multiple iterations in one node instead of using multiple blur nodes because it has better performance in the current implementation. We use the term "Blur" (instead of "Smooth") because smoothing is generally more related to removing roughness from surfaces. When viewing the result of the Blur Attribute node in the viewport, it looks like an image is blurred. While the node can also be used to smooth surfaces, other/better algorithms exists for that purpose (which e.g. don't reduce the volume of the mesh to zero with too many iterations). Differential Revision: https://developer.blender.org/D13952 --- .../startup/bl_ui/node_add_menu_geometry.py | 1 + source/blender/blenkernel/BKE_node.h | 1 + source/blender/makesrna/intern/rna_nodetree.c | 26 + source/blender/nodes/NOD_static_types.h | 1 + source/blender/nodes/geometry/CMakeLists.txt | 1 + .../nodes/geometry/node_geometry_register.cc | 1 + .../nodes/geometry/node_geometry_register.hh | 1 + .../geometry/nodes/node_geo_blur_attribute.cc | 497 ++++++++++++++++++ 8 files changed, 529 insertions(+) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_blur_attribute.cc diff --git a/release/scripts/startup/bl_ui/node_add_menu_geometry.py b/release/scripts/startup/bl_ui/node_add_menu_geometry.py index 83448f8e32a..d75d617c3ac 100644 --- a/release/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/release/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -12,6 +12,7 @@ class NODE_MT_geometry_node_GEO_ATTRIBUTE(Menu): def draw(self, _context): layout = self.layout node_add_menu.add_node_type(layout, "GeometryNodeAttributeStatistic") + node_add_menu.add_node_type(layout, "GeometryNodeBlurAttribute") node_add_menu.add_node_type(layout, "GeometryNodeCaptureAttribute") node_add_menu.add_node_type(layout, "GeometryNodeAttributeDomainSize") node_add_menu.add_node_type(layout, "GeometryNodeRemoveAttribute") diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index e50d895d203..88f9c801aa8 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1541,6 +1541,7 @@ struct TexResult; #define GEO_NODE_SAMPLE_UV_SURFACE 1187 #define GEO_NODE_SET_CURVE_NORMAL 1188 #define GEO_NODE_IMAGE_INFO 1189 +#define GEO_NODE_BLUR_ATTRIBUTE 1190 /** \} */ diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 4a0094831b9..3e0ca04a0b2 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -2202,6 +2202,19 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeType_type_with_socket_it generic_attribute_type_supported_with_socket); } +static bool rna_GeometryNodeBlurAttribute_data_type_supported(const EnumPropertyItem *item) +{ + return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_COLOR, CD_PROP_INT32); +} + +static const EnumPropertyItem *rna_GeometryNodeBlurAttribute_data_type_itemf( + bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_attribute_type_items, + rna_GeometryNodeBlurAttribute_data_type_supported); +} + static bool attribute_statistic_type_supported(const EnumPropertyItem *item) { return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3); @@ -9521,6 +9534,19 @@ static void def_geo_accumulate_field(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_blur_attribute(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeBlurAttribute_data_type_itemf"); + RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_ui_text(prop, "Data Type", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update"); +} + static void def_fn_random_value(StructRNA *srna) { PropertyRNA *prop; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 03cab5a778a..6b892dfc5cf 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -283,6 +283,7 @@ DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToStri DefNode(GeometryNode, GEO_NODE_ACCUMULATE_FIELD, def_geo_accumulate_field, "ACCUMULATE_FIELD", AccumulateField, "Accumulate Field", "Add the values of an evaluated field together and output the running total for each element") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_DOMAIN_SIZE, def_geo_attribute_domain_size, "ATTRIBUTE_DOMAIN_SIZE", AttributeDomainSize, "Domain Size", "Retrieve the number of elements in a geometry for each attribute domain") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC",AttributeStatistic, "Attribute Statistic","Calculate statistics about a data set from a field evaluated on a geometry") +DefNode(GeometryNode, GEO_NODE_BLUR_ATTRIBUTE, def_geo_blur_attribute, "BLUR_ATTRIBUTE", BlurAttribute, "Blur Attribute", "Mix attribute values of neighboring elements") DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "Calculate the limits of a geometry's positions and generate a box mesh with those dimensions") DefNode(GeometryNode, GEO_NODE_CAPTURE_ATTRIBUTE, def_geo_attribute_capture,"CAPTURE_ATTRIBUTE", CaptureAttribute, "Capture Attribute", "Store the result of a field on a geometry and output the data as a node socket. Allows remembering or interpolating data as the geometry changes, such as positions before deformation") DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "Retrieve geometry from a collection") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 2118ba778be..840be95ce1a 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -29,6 +29,7 @@ set(SRC nodes/node_geo_attribute_capture.cc nodes/node_geo_attribute_domain_size.cc nodes/node_geo_attribute_statistic.cc + nodes/node_geo_blur_attribute.cc nodes/node_geo_boolean.cc nodes/node_geo_bounding_box.cc nodes/node_geo_collection_info.cc diff --git a/source/blender/nodes/geometry/node_geometry_register.cc b/source/blender/nodes/geometry/node_geometry_register.cc index 8b011547fe2..da3f0966eee 100644 --- a/source/blender/nodes/geometry/node_geometry_register.cc +++ b/source/blender/nodes/geometry/node_geometry_register.cc @@ -14,6 +14,7 @@ void register_geometry_nodes() register_node_type_geo_attribute_capture(); register_node_type_geo_attribute_domain_size(); register_node_type_geo_attribute_statistic(); + register_node_type_geo_blur_attribute(); register_node_type_geo_boolean(); register_node_type_geo_bounding_box(); register_node_type_geo_collection_info(); diff --git a/source/blender/nodes/geometry/node_geometry_register.hh b/source/blender/nodes/geometry/node_geometry_register.hh index 2c94e60a2ec..b7cca9e1903 100644 --- a/source/blender/nodes/geometry/node_geometry_register.hh +++ b/source/blender/nodes/geometry/node_geometry_register.hh @@ -11,6 +11,7 @@ void register_node_type_geo_attribute_capture(); void register_node_type_geo_attribute_domain_size(); void register_node_type_geo_attribute_separate_xyz(); void register_node_type_geo_attribute_statistic(); +void register_node_type_geo_blur_attribute(); void register_node_type_geo_boolean(); void register_node_type_geo_bounding_box(); void register_node_type_geo_collection_info(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_blur_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_blur_attribute.cc new file mode 100644 index 00000000000..f53364aebdb --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_blur_attribute.cc @@ -0,0 +1,497 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_array.hh" +#include "BLI_generic_array.hh" +#include "BLI_index_mask.hh" +#include "BLI_index_range.hh" +#include "BLI_span.hh" +#include "BLI_task.hh" +#include "BLI_vector.hh" +#include "BLI_virtual_array.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_curves.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "NOD_socket_search_link.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_blur_attribute_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input(N_("Value"), "Value_Float") + .supports_field() + .hide_value() + .is_default_link_socket(); + b.add_input(N_("Value"), "Value_Int") + .supports_field() + .hide_value() + .is_default_link_socket(); + b.add_input(N_("Value"), "Value_Vector") + .supports_field() + .hide_value() + .is_default_link_socket(); + b.add_input(N_("Value"), "Value_Color") + .supports_field() + .hide_value() + .is_default_link_socket(); + + b.add_input("Iterations") + .default_value(1) + .min(0) + .description(N_("How many times to blur the values for all elements")); + b.add_input("Weight") + .default_value(1.0f) + .subtype(PROP_FACTOR) + .min(0.0f) + .max(1.0f) + .supports_field() + .description(N_("Relative mix weight of neighboring elements")); + + b.add_output(N_("Value"), "Value_Float").field_source().dependent_field(); + b.add_output(N_("Value"), "Value_Int").field_source().dependent_field(); + b.add_output(N_("Value"), "Value_Vector").field_source().dependent_field(); + b.add_output(N_("Value"), "Value_Color").field_source().dependent_field(); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = CD_PROP_FLOAT; +} + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + search_link_ops_for_declarations(params, declaration.inputs().take_back(2)); + + const bNodeType &node_type = params.node_type(); + const std::optional type = node_data_type_to_custom_data_type( + (eNodeSocketDatatype)params.other_socket().type); + if (type && *type != CD_PROP_STRING) { + params.add_item(IFACE_("Value"), [node_type, type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node(node_type); + node.custom1 = *type; + params.update_and_connect_available_socket(node, "Value"); + }); + } +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const eCustomDataType data_type = static_cast(node->custom1); + + bNodeSocket *socket_value_float = (bNodeSocket *)node->inputs.first; + bNodeSocket *socket_value_int32 = socket_value_float->next; + bNodeSocket *socket_value_vector = socket_value_int32->next; + bNodeSocket *socket_value_color4f = socket_value_vector->next; + + nodeSetSocketAvailability(ntree, socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_value_int32, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_value_color4f, data_type == CD_PROP_COLOR); + + bNodeSocket *out_socket_value_float = (bNodeSocket *)node->outputs.first; + bNodeSocket *out_socket_value_int32 = out_socket_value_float->next; + bNodeSocket *out_socket_value_vector = out_socket_value_int32->next; + bNodeSocket *out_socket_value_color4f = out_socket_value_vector->next; + + nodeSetSocketAvailability(ntree, out_socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, out_socket_value_int32, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, out_socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, out_socket_value_color4f, data_type == CD_PROP_COLOR); +} + +static Array> build_vert_to_vert_by_edge_map(const Span edges, + const int verts_num) +{ + Array> map(verts_num); + for (const MEdge &edge : edges) { + map[edge.v1].append(edge.v2); + map[edge.v2].append(edge.v1); + } + return map; +} + +static Array> build_edge_to_edge_by_vert_map(const Span edges, + const int verts_num, + const IndexMask edge_mask) +{ + Array> map(edges.size()); + Array> vert_to_edge_map = bke::mesh_topology::build_vert_to_edge_map(edges, + verts_num); + + threading::parallel_for(edge_mask.index_range(), 1024, [&](IndexRange range) { + for (const int edge_i : edge_mask.slice(range)) { + + Vector &self_edges = map[edge_i]; + const Span vert_1_edges = vert_to_edge_map[edges[edge_i].v1]; + const Span vert_2_edges = vert_to_edge_map[edges[edge_i].v2]; + + self_edges.reserve(vert_1_edges.size() - 1 + vert_2_edges.size() - 1); + + for (const int i : vert_1_edges) { + if (i != edge_i) { + self_edges.append(i); + } + } + for (const int i : vert_2_edges) { + if (i != edge_i) { + self_edges.append(i); + } + } + } + }); + return map; +} + +static Array> build_face_to_edge_by_loop_map(const Span polys, + const Span loops, + const int edges_num) +{ + Array> map(edges_num); + for (const int i : polys.index_range()) { + const MPoly &poly = polys[i]; + for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + map[loop.e].append(i); + } + } + return map; +} + +static Array> build_face_to_face_by_edge_map(const Span polys, + const Span loops, + const int edges_num, + const IndexMask poly_mask) +{ + Array> map(polys.size()); + Array> faces_by_edge = build_face_to_edge_by_loop_map(polys, loops, edges_num); + + threading::parallel_for(poly_mask.index_range(), 1024, [&](IndexRange range) { + for (const int poly_i : poly_mask.slice(range)) { + const MPoly &poly = polys[poly_i]; + for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + const int edge_i = loop.e; + if (faces_by_edge[edge_i].size() > 1) { + for (const int neighbor : faces_by_edge[edge_i]) { + if (neighbor != poly_i) { + map[poly_i].append(neighbor); + } + } + } + } + } + }); + return map; +} + +static Array> create_mesh_map(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask mask) +{ + switch (domain) { + case ATTR_DOMAIN_POINT: { + const Span edges = mesh.edges(); + const int verts_num = mesh.totvert; + return build_vert_to_vert_by_edge_map(edges, verts_num); + } + case ATTR_DOMAIN_EDGE: { + const Span edges = mesh.edges(); + const int verts_num = mesh.totvert; + return build_edge_to_edge_by_vert_map(edges, verts_num, mask); + } + case ATTR_DOMAIN_FACE: { + const Span polys = mesh.polys(); + const Span loops = mesh.loops(); + const int edges_num = mesh.totedge; + return build_face_to_face_by_edge_map(polys, loops, edges_num, mask); + } + case ATTR_DOMAIN_CORNER: { + return {}; + } + default: + BLI_assert_unreachable(); + return {}; + } +} + +template +static void blur_on_mesh_exec(const Span neighbor_weights, + const Span> neighbors_map, + const int iterations, + MutableSpan main_buffer, + MutableSpan tmp_buffer) +{ + MutableSpan src = main_buffer; + MutableSpan dst = tmp_buffer; + + for ([[maybe_unused]] const int64_t iteration : IndexRange(iterations)) { + attribute_math::DefaultMixer mixer{dst, IndexMask(0)}; + threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) { + for (const int64_t index : range) { + const Span neighbors = neighbors_map[index]; + const float neighbor_weight = neighbor_weights[index]; + mixer.set(index, src[index], 1.0f); + for (const int neighbor : neighbors) { + mixer.mix_in(index, src[neighbor], neighbor_weight); + } + } + mixer.finalize(range); + }); + std::swap(src, dst); + } + + /* The last computed values are in #src now. If the main buffer is #dst, the values have to be + * copied once more. */ + if (dst.data() == main_buffer.data()) { + threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) { + initialized_copy_n( + src.data() + range.start(), range.size(), main_buffer.data() + range.start()); + }); + } +} + +static void blur_on_mesh(const Mesh &mesh, + const eAttrDomain domain, + const int iterations, + const Span neighbor_weights, + GMutableSpan main_buffer, + GMutableSpan tmp_buffer) +{ + Array> neighbors_map = create_mesh_map(mesh, domain, neighbor_weights.index_range()); + if (neighbors_map.is_empty()) { + return; + } + attribute_math::convert_to_static_type(main_buffer.type(), [&](auto dummy) { + using T = decltype(dummy); + blur_on_mesh_exec(neighbor_weights, + neighbors_map, + iterations, + main_buffer.typed(), + tmp_buffer.typed()); + }); +} + +template +static void blur_on_curve_exec(const bke::CurvesGeometry &curves, + const Span neighbor_weights, + const int iterations, + MutableSpan main_buffer, + MutableSpan tmp_buffer) +{ + MutableSpan src = main_buffer; + MutableSpan dst = tmp_buffer; + + const VArray cyclic = curves.cyclic(); + + for ([[maybe_unused]] const int iteration : IndexRange(iterations)) { + attribute_math::DefaultMixer mixer{dst, IndexMask(0)}; + threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = curves.points_for_curve(curve_i); + if (points.size() == 1) { + /* No mixing possible. */ + const int point_i = points[0]; + mixer.set(point_i, src[point_i], 1.0f); + continue; + } + /* Inner points. */ + for (const int point_i : points.drop_front(1).drop_back(1)) { + const float neighbor_weight = neighbor_weights[point_i]; + mixer.set(point_i, src[point_i], 1.0f); + mixer.mix_in(point_i, src[point_i - 1], neighbor_weight); + mixer.mix_in(point_i, src[point_i + 1], neighbor_weight); + } + const int first_i = points[0]; + const float first_neighbor_weight = neighbor_weights[first_i]; + const int last_i = points.last(); + const float last_neighbor_weight = neighbor_weights[last_i]; + if (cyclic[curve_i]) { + /* First point. */ + mixer.set(first_i, src[first_i], 1.0f); + mixer.mix_in(first_i, src[first_i + 1], first_neighbor_weight); + mixer.mix_in(first_i, src[last_i], first_neighbor_weight); + /* Last point. */ + mixer.set(last_i, src[last_i], 1.0f); + mixer.mix_in(last_i, src[last_i - 1], last_neighbor_weight); + mixer.mix_in(last_i, src[first_i], last_neighbor_weight); + } + else { + /* First point. */ + mixer.set(first_i, src[first_i], 1.0f); + mixer.mix_in(first_i, src[first_i + 1], first_neighbor_weight); + /* Last point. */ + mixer.set(last_i, src[last_i], 1.0f); + mixer.mix_in(last_i, src[last_i - 1], last_neighbor_weight); + } + } + mixer.finalize(curves.points_for_curves(range)); + }); + std::swap(src, dst); + } + + /* The last computed values are in #src now. If the main buffer is #dst, the values have to be + * copied once more. */ + if (dst.data() == main_buffer.data()) { + threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) { + initialized_copy_n( + src.data() + range.start(), range.size(), main_buffer.data() + range.start()); + }); + } +} + +static void blur_on_curves(const bke::CurvesGeometry &curves, + const int iterations, + const Span neighbor_weights, + GMutableSpan main_buffer, + GMutableSpan tmp_buffer) +{ + attribute_math::convert_to_static_type(main_buffer.type(), [&](auto dummy) { + using T = decltype(dummy); + blur_on_curve_exec( + curves, neighbor_weights, iterations, main_buffer.typed(), tmp_buffer.typed()); + }); +} + +class BlurAttributeFieldInput final : public bke::GeometryFieldInput { + private: + const Field weight_field_; + const GField value_field_; + const int iterations_; + + public: + BlurAttributeFieldInput(Field weight_field, GField value_field, const int iterations) + : bke::GeometryFieldInput(value_field.cpp_type(), "Blur Attribute"), + weight_field_(std::move(weight_field)), + value_field_(std::move(value_field)), + iterations_(iterations) + { + } + + GVArray get_varray_for_context(const bke::GeometryFieldContext &context, + const IndexMask /*mask*/) const final + { + const int64_t domain_size = context.attributes()->domain_size(context.domain()); + + GArray<> main_buffer(*type_, domain_size); + + FieldEvaluator evaluator(context, domain_size); + + evaluator.add_with_destination(value_field_, main_buffer.as_mutable_span()); + evaluator.add(weight_field_); + evaluator.evaluate(); + + /* Blurring does not make sense with a less than 2 elements. */ + if (domain_size <= 1) { + return GVArray::ForGArray(std::move(main_buffer)); + } + + if (iterations_ <= 0) { + return GVArray::ForGArray(std::move(main_buffer)); + } + + VArraySpan neighbor_weights = evaluator.get_evaluated(1); + GArray<> tmp_buffer(*type_, domain_size); + + switch (context.type()) { + case GEO_COMPONENT_TYPE_MESH: + if (ELEM(context.domain(), ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_FACE)) { + if (const Mesh *mesh = context.mesh()) { + blur_on_mesh( + *mesh, context.domain(), iterations_, neighbor_weights, main_buffer, tmp_buffer); + } + } + break; + case GEO_COMPONENT_TYPE_CURVE: + if (context.domain() == ATTR_DOMAIN_POINT) { + if (const bke::CurvesGeometry *curves = context.curves()) { + blur_on_curves(*curves, iterations_, neighbor_weights, main_buffer, tmp_buffer); + } + } + break; + default: + break; + } + + return GVArray::ForGArray(std::move(main_buffer)); + } + + uint64_t hash() const override + { + return get_default_hash_3(iterations_, weight_field_, value_field_); + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (const BlurAttributeFieldInput *other_blur = dynamic_cast( + &other)) { + return weight_field_ == other_blur->weight_field_ && + value_field_ == other_blur->value_field_ && iterations_ == other_blur->iterations_; + } + return false; + } +}; + +static StringRefNull identifier_suffix(eCustomDataType data_type) +{ + switch (data_type) { + case CD_PROP_FLOAT: + return "Float"; + case CD_PROP_INT32: + return "Int"; + case CD_PROP_COLOR: + return "Color"; + case CD_PROP_FLOAT3: + return "Vector"; + default: + BLI_assert_unreachable(); + return ""; + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const eCustomDataType data_type = static_cast(params.node().custom1); + + const int iterations = params.extract_input("Iterations"); + Field weight_field = params.extract_input>("Weight"); + + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + static const std::string identifier = "Value_" + identifier_suffix(data_type); + Field value_field = params.extract_input>(identifier); + Field output_field{std::make_shared( + std::move(weight_field), std::move(value_field), iterations)}; + params.set_output(identifier, std::move(output_field)); + }); +} + +} // namespace blender::nodes::node_geo_blur_attribute_cc + +void register_node_type_geo_blur_attribute() +{ + namespace file_ns = blender::nodes::node_geo_blur_attribute_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_BLUR_ATTRIBUTE, "Blur Attribute", NODE_CLASS_ATTRIBUTE); + ntype.declare = file_ns::node_declare; + ntype.initfunc = file_ns::node_init; + ntype.updatefunc = file_ns::node_update; + ntype.draw_buttons = file_ns::node_layout; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; + nodeRegisterType(&ntype); +}