Blender V4.3
node_geo_blur_attribute.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_array.hh"
7#include "BLI_index_mask.hh"
8#include "BLI_index_range.hh"
9#include "BLI_span.hh"
10#include "BLI_task.hh"
11#include "BLI_vector.hh"
12#include "BLI_virtual_array.hh"
13
14#include "BKE_attribute_math.hh"
15#include "BKE_curves.hh"
17#include "BKE_grease_pencil.hh"
18#include "BKE_mesh.hh"
19#include "BKE_mesh_mapping.hh"
20
21#include "NOD_rna_define.hh"
22
23#include "UI_interface.hh"
24#include "UI_resources.hh"
25
26#include "RNA_enum_types.hh"
27
29
30#include "node_geometry_util.hh"
31
33
35{
36 const bNode *node = b.node_or_null();
37
38 if (node != nullptr) {
39 const eCustomDataType data_type = eCustomDataType(node->custom1);
40 b.add_input(data_type, "Value").supports_field().hide_value().is_default_link_socket();
41 }
42 b.add_input<decl::Int>("Iterations")
43 .default_value(1)
44 .min(0)
45 .description("How many times to blur the values for all elements");
46 b.add_input<decl::Float>("Weight")
47 .default_value(1.0f)
49 .min(0.0f)
50 .max(1.0f)
51 .supports_field()
52 .description("Relative mix weight of neighboring elements");
53
54 if (node != nullptr) {
55 const eCustomDataType data_type = eCustomDataType(node->custom1);
56 b.add_output(data_type, "Value").field_source_reference_all().dependent_field();
57 }
58}
59
60static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
61{
62 uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
63}
64
65static void node_init(bNodeTree * /*tree*/, bNode *node)
66{
67 node->custom1 = CD_PROP_FLOAT;
68}
69
71{
72 const blender::bke::bNodeType &node_type = params.node_type();
73 const NodeDeclaration &declaration = *node_type.static_declaration;
74
75 /* Weight and Iterations inputs don't change based on the data type. */
77
78 const std::optional<eCustomDataType> new_node_type = bke::socket_type_to_custom_data_type(
79 eNodeSocketDatatype(params.other_socket().type));
80 if (!new_node_type.has_value()) {
81 return;
82 }
83 eCustomDataType fixed_data_type = *new_node_type;
84 if (fixed_data_type == CD_PROP_STRING) {
85 return;
86 }
87 if (fixed_data_type == CD_PROP_QUATERNION) {
88 fixed_data_type = CD_PROP_FLOAT3;
89 }
90 if (fixed_data_type == CD_PROP_FLOAT4X4) {
91 /* Don't implement matrix blurring for now. */
92 return;
93 }
94 if (fixed_data_type == CD_PROP_BOOL) {
95 /* This node does not support boolean sockets, use integer instead. */
96 fixed_data_type = CD_PROP_INT32;
97 }
98 params.add_item(IFACE_("Value"), [node_type, fixed_data_type](LinkSearchOpParams &params) {
99 bNode &node = params.add_node(node_type);
100 node.custom1 = fixed_data_type;
101 params.update_and_connect_available_socket(node, "Value");
102 });
103}
104
106 const int verts_num,
107 Array<int> &r_offsets,
108 Array<int> &r_indices)
109{
110 bke::mesh::build_vert_to_edge_map(edges, verts_num, r_offsets, r_indices);
111 const OffsetIndices<int> offsets(r_offsets);
112 threading::parallel_for(IndexRange(verts_num), 2048, [&](const IndexRange range) {
113 for (const int vert : range) {
114 MutableSpan<int> neighbors = r_indices.as_mutable_span().slice(offsets[vert]);
115 for (const int i : neighbors.index_range()) {
116 neighbors[i] = bke::mesh::edge_other_vert(edges[neighbors[i]], vert);
117 }
118 }
119 });
120}
121
123 const int verts_num,
124 Array<int> &r_offsets,
125 Array<int> &r_indices)
126{
127 Array<int> vert_to_edge_offset_data;
128 Array<int> vert_to_edge_indices;
130 edges, verts_num, vert_to_edge_offset_data, vert_to_edge_indices);
131 const OffsetIndices<int> vert_to_edge_offsets(vert_to_edge_offset_data);
132
133 r_offsets = Array<int>(edges.size() + 1, 0);
134 threading::parallel_for(edges.index_range(), 1024, [&](const IndexRange range) {
135 for (const int edge_i : range) {
136 const int2 edge = edges[edge_i];
137 r_offsets[edge_i] = vert_to_edge_offsets[edge[0]].size() - 1 +
138 vert_to_edge_offsets[edge[1]].size() - 1;
139 }
140 });
142 r_indices.reinitialize(offsets.total_size());
143
144 threading::parallel_for(edges.index_range(), 1024, [&](const IndexRange range) {
145 for (const int edge_i : range) {
146 const int2 edge = edges[edge_i];
147 MutableSpan<int> neighbors = r_indices.as_mutable_span().slice(offsets[edge_i]);
148 int count = 0;
149 for (const Span<int> neighbor_edges : {vert_to_edge[edge[0]], vert_to_edge[edge[1]]}) {
150 for (const int neighbor_edge : neighbor_edges) {
151 if (neighbor_edge != edge_i) {
152 neighbors[count] = neighbor_edge;
153 count++;
154 }
155 }
156 }
157 }
158 });
159}
160
162 const Span<int> corner_edges,
163 const int edges_num,
164 Array<int> &r_offsets,
165 Array<int> &r_indices)
166{
167 Array<int> edge_to_face_offset_data;
168 Array<int> edge_to_face_indices;
169 const GroupedSpan<int> edge_to_face_map = bke::mesh::build_edge_to_face_map(
170 faces, corner_edges, edges_num, edge_to_face_offset_data, edge_to_face_indices);
171 const OffsetIndices<int> edge_to_face_offsets(edge_to_face_offset_data);
172
173 r_offsets = Array<int>(faces.size() + 1, 0);
174 threading::parallel_for(faces.index_range(), 4096, [&](const IndexRange range) {
175 for (const int face_i : range) {
176 for (const int edge : corner_edges.slice(faces[face_i])) {
177 /* Subtract face itself from the number of faces connected to the edge. */
178 r_offsets[face_i] += edge_to_face_offsets[edge].size() - 1;
179 }
180 }
181 });
182 const OffsetIndices<int> offsets = offset_indices::accumulate_counts_to_offsets(r_offsets);
183 r_indices.reinitialize(offsets.total_size());
184
185 threading::parallel_for(faces.index_range(), 1024, [&](IndexRange range) {
186 for (const int face_i : range) {
187 MutableSpan<int> neighbors = r_indices.as_mutable_span().slice(offsets[face_i]);
188 if (neighbors.is_empty()) {
189 continue;
190 }
191 int count = 0;
192 for (const int edge : corner_edges.slice(faces[face_i])) {
193 for (const int neighbor : edge_to_face_map[edge]) {
194 if (neighbor != face_i) {
195 neighbors[count] = neighbor;
196 count++;
197 }
198 }
199 }
200 }
201 });
202}
203
205 const AttrDomain domain,
206 Array<int> &r_offsets,
207 Array<int> &r_indices)
208{
209 switch (domain) {
210 case AttrDomain::Point:
211 build_vert_to_vert_by_edge_map(mesh.edges(), mesh.verts_num, r_offsets, r_indices);
212 break;
213 case AttrDomain::Edge:
214 build_edge_to_edge_by_vert_map(mesh.edges(), mesh.verts_num, r_offsets, r_indices);
215 break;
216 case AttrDomain::Face:
218 mesh.faces(), mesh.corner_edges(), mesh.edges_num, r_offsets, r_indices);
219 break;
220 default:
222 break;
223 }
224 return {OffsetIndices<int>(r_offsets), r_indices};
225}
226
227template<typename T>
228static Span<T> blur_on_mesh_exec(const Span<float> neighbor_weights,
229 const GroupedSpan<int> neighbors_map,
230 const int iterations,
231 const MutableSpan<T> buffer_a,
232 const MutableSpan<T> buffer_b)
233{
234 /* Source is set to buffer_b even though it is actually in buffer_a because the loop below starts
235 * with swapping both. */
236 MutableSpan<T> src = buffer_b;
237 MutableSpan<T> dst = buffer_a;
238
239 for ([[maybe_unused]] const int64_t iteration : IndexRange(iterations)) {
240 std::swap(src, dst);
242 threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
243 for (const int64_t index : range) {
244 const Span<int> neighbors = neighbors_map[index];
245 const float neighbor_weight = neighbor_weights[index];
246 mixer.set(index, src[index], 1.0f);
247 for (const int neighbor : neighbors) {
248 mixer.mix_in(index, src[neighbor], neighbor_weight);
249 }
250 }
251 mixer.finalize(range);
252 });
253 }
254
255 return dst;
256}
257
258template<typename Func> static void to_static_type_for_blur(const CPPType &type, const Func &func)
259{
260 type.to_static_type_tag<int, float, float3, ColorGeometry4f>([&](auto type_tag) {
261 using T = typename decltype(type_tag)::type;
262 if constexpr (!std::is_same_v<T, void>) {
263 func(T());
264 }
265 else {
267 }
268 });
269}
270
271static GSpan blur_on_mesh(const Mesh &mesh,
272 const AttrDomain domain,
273 const int iterations,
274 const Span<float> neighbor_weights,
275 const GMutableSpan buffer_a,
276 const GMutableSpan buffer_b)
277{
278 Array<int> neighbor_offsets;
279 Array<int> neighbor_indices;
280 const GroupedSpan<int> neighbors_map = create_mesh_map(
281 mesh, domain, neighbor_offsets, neighbor_indices);
282
283 GSpan result_buffer;
284 to_static_type_for_blur(buffer_a.type(), [&](auto dummy) {
285 using T = decltype(dummy);
286 result_buffer = blur_on_mesh_exec<T>(
287 neighbor_weights, neighbors_map, iterations, buffer_a.typed<T>(), buffer_b.typed<T>());
288 });
289 return result_buffer;
290}
291
292template<typename T>
294 const Span<float> neighbor_weights,
295 const int iterations,
296 const MutableSpan<T> buffer_a,
297 const MutableSpan<T> buffer_b)
298{
299 MutableSpan<T> src = buffer_b;
300 MutableSpan<T> dst = buffer_a;
301
302 const OffsetIndices points_by_curve = curves.points_by_curve();
303 const VArray<bool> cyclic = curves.cyclic();
304
305 for ([[maybe_unused]] const int iteration : IndexRange(iterations)) {
306 std::swap(src, dst);
308 threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) {
309 for (const int curve_i : range) {
310 const IndexRange points = points_by_curve[curve_i];
311 if (points.size() == 1) {
312 /* No mixing possible. */
313 const int point_i = points[0];
314 mixer.set(point_i, src[point_i], 1.0f);
315 continue;
316 }
317 /* Inner points. */
318 for (const int point_i : points.drop_front(1).drop_back(1)) {
319 const float neighbor_weight = neighbor_weights[point_i];
320 mixer.set(point_i, src[point_i], 1.0f);
321 mixer.mix_in(point_i, src[point_i - 1], neighbor_weight);
322 mixer.mix_in(point_i, src[point_i + 1], neighbor_weight);
323 }
324 const int first_i = points[0];
325 const float first_neighbor_weight = neighbor_weights[first_i];
326 const int last_i = points.last();
327 const float last_neighbor_weight = neighbor_weights[last_i];
328
329 /* First point. */
330 mixer.set(first_i, src[first_i], 1.0f);
331 mixer.mix_in(first_i, src[first_i + 1], first_neighbor_weight);
332 /* Last point. */
333 mixer.set(last_i, src[last_i], 1.0f);
334 mixer.mix_in(last_i, src[last_i - 1], last_neighbor_weight);
335
336 if (cyclic[curve_i]) {
337 /* First point. */
338 mixer.mix_in(first_i, src[last_i], first_neighbor_weight);
339 /* Last point. */
340 mixer.mix_in(last_i, src[first_i], last_neighbor_weight);
341 }
342 }
343 mixer.finalize(points_by_curve[range]);
344 });
345 }
346
347 return dst;
348}
349
351 const int iterations,
352 const Span<float> neighbor_weights,
353 const GMutableSpan buffer_a,
354 const GMutableSpan buffer_b)
355{
356 GSpan result_buffer;
357 to_static_type_for_blur(buffer_a.type(), [&](auto dummy) {
358 using T = decltype(dummy);
359 result_buffer = blur_on_curve_exec<T>(
360 curves, neighbor_weights, iterations, buffer_a.typed<T>(), buffer_b.typed<T>());
361 });
362 return result_buffer;
363}
364
366 private:
367 const Field<float> weight_field_;
368 const GField value_field_;
369 const int iterations_;
370
371 public:
372 BlurAttributeFieldInput(Field<float> weight_field, GField value_field, const int iterations)
373 : bke::GeometryFieldInput(value_field.cpp_type(), "Blur Attribute"),
374 weight_field_(std::move(weight_field)),
375 value_field_(std::move(value_field)),
376 iterations_(iterations)
377 {
378 }
379
381 const IndexMask & /*mask*/) const final
382 {
383 const int64_t domain_size = context.attributes()->domain_size(context.domain());
384
385 GArray<> buffer_a(*type_, domain_size);
386
387 FieldEvaluator evaluator(context, domain_size);
388
389 evaluator.add_with_destination(value_field_, buffer_a.as_mutable_span());
390 evaluator.add(weight_field_);
391 evaluator.evaluate();
392
393 /* Blurring does not make sense with a less than 2 elements. */
394 if (domain_size <= 1) {
395 return GVArray::ForGArray(std::move(buffer_a));
396 }
397
398 if (iterations_ <= 0) {
399 return GVArray::ForGArray(std::move(buffer_a));
400 }
401
402 VArraySpan<float> neighbor_weights = evaluator.get_evaluated<float>(1);
403 GArray<> buffer_b(*type_, domain_size);
404
405 GSpan result_buffer = buffer_a.as_span();
406 switch (context.type()) {
407 case GeometryComponent::Type::Mesh:
408 if (ELEM(context.domain(), AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face)) {
409 if (const Mesh *mesh = context.mesh()) {
410 result_buffer = blur_on_mesh(
411 *mesh, context.domain(), iterations_, neighbor_weights, buffer_a, buffer_b);
412 }
413 }
414 break;
415 case GeometryComponent::Type::Curve:
416 case GeometryComponent::Type::GreasePencil:
417 if (context.domain() == AttrDomain::Point) {
418 if (const bke::CurvesGeometry *curves = context.curves_or_strokes()) {
419 result_buffer = blur_on_curves(
420 *curves, iterations_, neighbor_weights, buffer_a, buffer_b);
421 }
422 }
423 break;
424 default:
425 break;
426 }
427
428 BLI_assert(ELEM(result_buffer.data(), buffer_a.data(), buffer_b.data()));
429 if (result_buffer.data() == buffer_a.data()) {
430 return GVArray::ForGArray(std::move(buffer_a));
431 }
432 return GVArray::ForGArray(std::move(buffer_b));
433 }
434
435 void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
436 {
437 weight_field_.node().for_each_field_input_recursive(fn);
438 value_field_.node().for_each_field_input_recursive(fn);
439 }
440
441 uint64_t hash() const override
442 {
443 return get_default_hash(iterations_, weight_field_, value_field_);
444 }
445
446 bool is_equal_to(const fn::FieldNode &other) const override
447 {
448 if (const BlurAttributeFieldInput *other_blur = dynamic_cast<const BlurAttributeFieldInput *>(
449 &other))
450 {
451 return weight_field_ == other_blur->weight_field_ &&
452 value_field_ == other_blur->value_field_ && iterations_ == other_blur->iterations_;
453 }
454 return false;
455 }
456
457 std::optional<AttrDomain> preferred_domain(const GeometryComponent &component) const override
458 {
459 const std::optional<AttrDomain> domain = bke::try_detect_field_domain(component, value_field_);
460 if (domain.has_value() && *domain == AttrDomain::Corner) {
461 return AttrDomain::Point;
462 }
463 return domain;
464 }
465};
466
468{
469 const int iterations = params.extract_input<int>("Iterations");
470 Field<float> weight_field = params.extract_input<Field<float>>("Weight");
471
472 GField value_field = params.extract_input<GField>("Value");
473 GField output_field{std::make_shared<BlurAttributeFieldInput>(
474 std::move(weight_field), std::move(value_field), iterations)};
475 params.set_output<GField>("Value", std::move(output_field));
476}
477
478static void node_rna(StructRNA *srna)
479{
481 srna,
482 "data_type",
483 "Data Type",
484 "",
488 [](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
489 *r_free = true;
492 });
493 });
494}
495
496static void node_register()
497{
498 static blender::bke::bNodeType ntype;
499 geo_node_type_base(&ntype, GEO_NODE_BLUR_ATTRIBUTE, "Blur Attribute", NODE_CLASS_ATTRIBUTE);
500 ntype.initfunc = node_init;
501 ntype.declare = node_declare;
506
507 node_rna(ntype.rna_ext.srna);
508}
509NOD_REGISTER_NODE(node_register)
510
511} // namespace blender::nodes::node_geo_blur_attribute_cc
Low-level operations for curves.
Low-level operations for grease pencil.
#define NODE_CLASS_ATTRIBUTE
Definition BKE_node.hh:419
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define ELEM(...)
#define IFACE_(msgid)
@ CD_PROP_FLOAT
@ CD_PROP_FLOAT3
@ CD_PROP_COLOR
@ CD_PROP_QUATERNION
@ CD_PROP_INT32
@ CD_PROP_STRING
@ CD_PROP_FLOAT4X4
eNodeSocketDatatype
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_inline_enum_accessors(member)
@ PROP_FACTOR
Definition RNA_types.hh:154
#define UI_ITEM_NONE
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
GMutableSpan as_mutable_span()
GSpan as_span() const
const void * data() const
const CPPType & type() const
const void * data() const
constexpr IndexRange index_range() const
Definition BLI_span.hh:671
int add(GField field, GVArray *varray_ptr)
Definition field.cc:756
int add_with_destination(GField field, GVMutableArray dst)
Definition field.cc:743
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:450
virtual void for_each_field_input_recursive(FunctionRef< void(const FieldInput &)> fn) const
Definition field.cc:587
const FieldNode & node() const
Definition FN_field.hh:137
Vector< SocketDeclaration * > inputs
std::optional< AttrDomain > preferred_domain(const GeometryComponent &component) const override
BlurAttributeFieldInput(Field< float > weight_field, GField value_field, const int iterations)
void for_each_field_input_recursive(FunctionRef< void(const FieldInput &)> fn) const override
GVArray get_varray_for_context(const bke::GeometryFieldContext &context, const IndexMask &) const final
local_group_size(16, 16) .push_constant(Type b
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
IndexRange range
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define T
typename DefaultMixerStruct< T >::type DefaultMixer
int edge_other_vert(const int2 edge, const int vert)
Definition BKE_mesh.hh:307
GroupedSpan< int > build_vert_to_edge_map(Span< int2 > edges, int verts_num, Array< int > &r_offsets, Array< int > &r_indices)
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
std::optional< eCustomDataType > socket_type_to_custom_data_type(eNodeSocketDatatype type)
Definition node.cc:4379
static void node_geo_exec(GeoNodeExecParams params)
static Span< T > blur_on_curve_exec(const bke::CurvesGeometry &curves, const Span< float > neighbor_weights, const int iterations, const MutableSpan< T > buffer_a, const MutableSpan< T > buffer_b)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static void build_vert_to_vert_by_edge_map(const Span< int2 > edges, const int verts_num, Array< int > &r_offsets, Array< int > &r_indices)
static void to_static_type_for_blur(const CPPType &type, const Func &func)
static GSpan blur_on_mesh(const Mesh &mesh, const AttrDomain domain, const int iterations, const Span< float > neighbor_weights, const GMutableSpan buffer_a, const GMutableSpan buffer_b)
static GSpan blur_on_curves(const bke::CurvesGeometry &curves, const int iterations, const Span< float > neighbor_weights, const GMutableSpan buffer_a, const GMutableSpan buffer_b)
static GroupedSpan< int > create_mesh_map(const Mesh &mesh, const AttrDomain domain, Array< int > &r_offsets, Array< int > &r_indices)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static void build_face_to_face_by_edge_map(const OffsetIndices< int > faces, const Span< int > corner_edges, const int edges_num, Array< int > &r_offsets, Array< int > &r_indices)
static void build_edge_to_edge_by_vert_map(const Span< int2 > edges, const int verts_num, Array< int > &r_offsets, Array< int > &r_indices)
static void node_init(bNodeTree *, bNode *node)
static void node_declare(NodeDeclarationBuilder &b)
static Span< T > blur_on_mesh_exec(const Span< float > neighbor_weights, const GroupedSpan< int > neighbors_map, const int iterations, const MutableSpan< T > buffer_a, const MutableSpan< T > buffer_b)
void search_link_ops_for_declarations(GatherLinkSearchOpParams &params, Span< SocketDeclaration * > declarations)
PropertyRNA * RNA_def_node_enum(StructRNA *srna, const char *identifier, const char *ui_name, const char *ui_description, const EnumPropertyItem *static_items, const EnumRNAAccessors accessors, std::optional< int > default_value, const EnumPropertyItemFunc item_func, const bool allow_animation)
const EnumPropertyItem * enum_items_filter(const EnumPropertyItem *original_item_array, FunctionRef< bool(const EnumPropertyItem &item)> fn)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
uint64_t get_default_hash(const T &v)
Definition BLI_hash.hh:219
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
const EnumPropertyItem rna_enum_attribute_type_items[]
__int64 int64_t
Definition stdint.h:89
unsigned __int64 uint64_t
Definition stdint.h:90
StructRNA * srna
Definition RNA_types.hh:780
Defines a node type.
Definition BKE_node.hh:218
blender::nodes::NodeDeclaration * static_declaration
Definition BKE_node.hh:356
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:267
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:339
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition BKE_node.hh:363
NodeDeclareFunction declare
Definition BKE_node.hh:347
PointerRNA * ptr
Definition wm_files.cc:4126