Blender V5.0
node_geo_accumulate_field.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
6
7#include "BLI_array.hh"
10
11#include "NOD_rna_define.hh"
13
14#include "RNA_enum_types.hh"
15
16#include "node_geometry_util.hh"
17
19#include "UI_resources.hh"
20
22
24
26{
27 const bNode *node = b.node_or_null();
28
29 if (node != nullptr) {
30 const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type);
31 BaseSocketDeclarationBuilder *value_declaration = nullptr;
32 switch (data_type) {
33 case CD_PROP_FLOAT3:
34 value_declaration = &b.add_input<decl::Vector>("Value").default_value({1.0f, 1.0f, 1.0f});
35 break;
36 case CD_PROP_FLOAT:
37 value_declaration = &b.add_input<decl::Float>("Value").default_value(1.0f);
38 break;
39 case CD_PROP_INT32:
40 value_declaration = &b.add_input<decl::Int>("Value").default_value(1);
41 break;
43 value_declaration = &b.add_input<decl::Matrix>("Value");
44 break;
45 default:
47 break;
48 }
49 value_declaration->supports_field().description("The values to be accumulated");
50 }
51
52 b.add_input<decl::Int>("Group ID", "Group Index")
53 .supports_field()
54 .hide_value()
55 .description("An index used to group values together for multiple separate accumulations");
56
57 if (node != nullptr) {
58 const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type);
59 b.add_output(data_type, "Leading")
60 .field_source_reference_all()
61 .description(
62 "The running total of values in the corresponding group, starting at the first value");
63 b.add_output(data_type, "Trailing")
64 .field_source_reference_all()
65 .description("The running total of values in the corresponding group, starting at zero");
66 b.add_output(data_type, "Total")
67 .field_source_reference_all()
68 .description("The total of all of the values in the corresponding group");
69 }
70}
71
72static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
73{
74 layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
75 layout->prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
76}
77
78static void node_init(bNodeTree * /*tree*/, bNode *node)
79{
81 data->data_type = CD_PROP_FLOAT;
82 data->domain = int16_t(AttrDomain::Point);
83 node->storage = data;
84}
85
86enum class AccumulationMode { Leading = 0, Trailing = 1 };
87
88static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
89{
90 switch (socket.type) {
91 case SOCK_FLOAT:
92 return CD_PROP_FLOAT;
93 case SOCK_BOOLEAN:
94 case SOCK_INT:
95 return CD_PROP_INT32;
96 case SOCK_VECTOR:
97 case SOCK_RGBA:
98 case SOCK_ROTATION:
99 return CD_PROP_FLOAT3;
100 case SOCK_MATRIX:
101 return CD_PROP_FLOAT4X4;
102 default:
103 return std::nullopt;
104 }
105}
106
108{
109 const NodeDeclaration &declaration = *params.node_type().static_declaration;
111
112 const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
113 if (!type) {
114 return;
115 }
116 if (params.in_out() == SOCK_OUT) {
117 params.add_item(
118 IFACE_("Leading"),
119 [type](LinkSearchOpParams &params) {
120 bNode &node = params.add_node("GeometryNodeAccumulateField");
121 node_storage(node).data_type = *type;
122 params.update_and_connect_available_socket(node, "Leading");
123 },
124 0);
125 params.add_item(
126 IFACE_("Trailing"),
127 [type](LinkSearchOpParams &params) {
128 bNode &node = params.add_node("GeometryNodeAccumulateField");
129 node_storage(node).data_type = *type;
130 params.update_and_connect_available_socket(node, "Trailing");
131 },
132 -1);
133 params.add_item(
134 IFACE_("Total"),
135 [type](LinkSearchOpParams &params) {
136 bNode &node = params.add_node("GeometryNodeAccumulateField");
137 node_storage(node).data_type = *type;
138 params.update_and_connect_available_socket(node, "Total");
139 },
140 -2);
141 }
142 else {
143 params.add_item(
144 IFACE_("Value"),
145 [type](LinkSearchOpParams &params) {
146 bNode &node = params.add_node("GeometryNodeAccumulateField");
147 node_storage(node).data_type = *type;
148 params.update_and_connect_available_socket(node, "Value");
149 },
150 0);
151 }
152}
153
154template<typename T> struct AccumulationInfo {
155 static inline const T initial_value = []() {
156 if constexpr (std::is_same_v<T, float4x4>) {
157 return float4x4::identity();
158 }
159 else {
160 return T();
161 }
162 }();
163
164 static T accumulate(const T &a, const T &b)
165 {
166 if constexpr (std::is_same_v<T, float4x4>) {
167 return a * b;
168 }
169 else {
170 return a + b;
171 }
172 }
173};
174
176 private:
177 GField input_;
178 Field<int> group_index_;
179 AttrDomain source_domain_;
180 AccumulationMode accumulation_mode_;
181
182 public:
183 AccumulateFieldInput(const AttrDomain source_domain,
185 Field<int> group_index,
186 AccumulationMode accumulation_mode)
187 : bke::GeometryFieldInput(input.cpp_type(), "Accumulation"),
188 input_(std::move(input)),
189 group_index_(std::move(group_index)),
190 source_domain_(source_domain),
191 accumulation_mode_(accumulation_mode)
192 {
193 }
194
196 const IndexMask & /*mask*/) const final
197 {
198 const AttributeAccessor attributes = *context.attributes();
199 const int64_t domain_size = attributes.domain_size(source_domain_);
200 if (domain_size == 0) {
201 return {};
202 }
203
204 const bke::GeometryFieldContext source_context{context, source_domain_};
205 fn::FieldEvaluator evaluator{source_context, domain_size};
206 evaluator.add(input_);
207 evaluator.add(group_index_);
208 evaluator.evaluate();
209 const GVArray g_values = evaluator.get_evaluated(0);
210 const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
211
212 GVArray g_output;
213
214 bke::attribute_math::convert_to_static_type(g_values.type(), [&](auto dummy) {
215 using T = decltype(dummy);
216 if constexpr (is_same_any_v<T, int, float, float3, float4x4>) {
217 Array<T> outputs(domain_size);
218 const VArray<T> values = g_values.typed<T>();
219
220 if (group_indices.is_single()) {
221 T accumulation = AccumulationInfo<T>::initial_value;
222 if (accumulation_mode_ == AccumulationMode::Leading) {
223 for (const int i : values.index_range()) {
224 accumulation = AccumulationInfo<T>::accumulate(accumulation, values[i]);
225 outputs[i] = accumulation;
226 }
227 }
228 else {
229 for (const int i : values.index_range()) {
230 outputs[i] = accumulation;
231 accumulation = AccumulationInfo<T>::accumulate(accumulation, values[i]);
232 }
233 }
234 }
235 else {
236 Map<int, T> accumulations;
237 if (accumulation_mode_ == AccumulationMode::Leading) {
238 for (const int i : values.index_range()) {
239 T &accumulation_value = accumulations.lookup_or_add(
240 group_indices[i], AccumulationInfo<T>::initial_value);
241 accumulation_value = AccumulationInfo<T>::accumulate(accumulation_value, values[i]);
242 outputs[i] = accumulation_value;
243 }
244 }
245 else {
246 for (const int i : values.index_range()) {
247 T &accumulation_value = accumulations.lookup_or_add(
248 group_indices[i], AccumulationInfo<T>::initial_value);
249 outputs[i] = accumulation_value;
250 accumulation_value = AccumulationInfo<T>::accumulate(accumulation_value, values[i]);
251 }
252 }
253 }
254
255 g_output = VArray<T>::from_container(std::move(outputs));
256 }
257 });
258
259 return attributes.adapt_domain(std::move(g_output), source_domain_, context.domain());
260 }
261
263 {
264 input_.node().for_each_field_input_recursive(fn);
265 group_index_.node().for_each_field_input_recursive(fn);
266 }
267
268 uint64_t hash() const override
269 {
270 return get_default_hash(input_, group_index_, source_domain_, accumulation_mode_);
271 }
272
273 bool is_equal_to(const fn::FieldNode &other) const override
274 {
275 if (const AccumulateFieldInput *other_accumulate = dynamic_cast<const AccumulateFieldInput *>(
276 &other))
277 {
278 return input_ == other_accumulate->input_ &&
279 group_index_ == other_accumulate->group_index_ &&
280 source_domain_ == other_accumulate->source_domain_ &&
281 accumulation_mode_ == other_accumulate->accumulation_mode_;
282 }
283 return false;
284 }
285
286 std::optional<AttrDomain> preferred_domain(
287 const GeometryComponent & /*component*/) const override
288 {
289 return source_domain_;
290 }
291};
292
294 private:
295 GField input_;
296 Field<int> group_index_;
297 AttrDomain source_domain_;
298
299 public:
300 TotalFieldInput(const AttrDomain source_domain, GField input, Field<int> group_index)
301 : bke::GeometryFieldInput(input.cpp_type(), "Total Value"),
302 input_(std::move(input)),
303 group_index_(std::move(group_index)),
304 source_domain_(source_domain)
305 {
306 }
307
309 const IndexMask & /*mask*/) const final
310 {
311 const AttributeAccessor attributes = *context.attributes();
312 const int64_t domain_size = attributes.domain_size(source_domain_);
313 if (domain_size == 0) {
314 return {};
315 }
316
317 const bke::GeometryFieldContext source_context{context, source_domain_};
318 fn::FieldEvaluator evaluator{source_context, domain_size};
319 evaluator.add(input_);
320 evaluator.add(group_index_);
321 evaluator.evaluate();
322 const GVArray g_values = evaluator.get_evaluated(0);
323 const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
324
325 GVArray g_outputs;
326
327 bke::attribute_math::convert_to_static_type(g_values.type(), [&](auto dummy) {
328 using T = decltype(dummy);
329 if constexpr (is_same_any_v<T, int, float, float3, float4x4>) {
330 const VArray<T> values = g_values.typed<T>();
331 if (group_indices.is_single()) {
332 T accumulation = AccumulationInfo<T>::initial_value;
333 for (const int i : values.index_range()) {
334 accumulation = AccumulationInfo<T>::accumulate(accumulation, values[i]);
335 }
336 g_outputs = VArray<T>::from_single(accumulation, domain_size);
337 }
338 else {
339 Map<int, T> accumulations;
340 for (const int i : values.index_range()) {
341 T &value = accumulations.lookup_or_add(group_indices[i],
342 AccumulationInfo<T>::initial_value);
343 value = AccumulationInfo<T>::accumulate(value, values[i]);
344 }
345 Array<T> outputs(domain_size);
346 for (const int i : values.index_range()) {
347 outputs[i] = accumulations.lookup(group_indices[i]);
348 }
349 g_outputs = VArray<T>::from_container(std::move(outputs));
350 }
351 }
352 });
353
354 return attributes.adapt_domain(std::move(g_outputs), source_domain_, context.domain());
355 }
356
358 {
359 input_.node().for_each_field_input_recursive(fn);
360 group_index_.node().for_each_field_input_recursive(fn);
361 }
362
363 uint64_t hash() const override
364 {
365 return get_default_hash(input_, group_index_, source_domain_);
366 }
367
368 bool is_equal_to(const fn::FieldNode &other) const override
369 {
370 if (const TotalFieldInput *other_field = dynamic_cast<const TotalFieldInput *>(&other)) {
371 return input_ == other_field->input_ && group_index_ == other_field->group_index_ &&
372 source_domain_ == other_field->source_domain_;
373 }
374 return false;
375 }
376
377 std::optional<AttrDomain> preferred_domain(
378 const GeometryComponent & /*component*/) const override
379 {
380 return source_domain_;
381 }
382};
383
385{
386 const NodeAccumulateField &storage = node_storage(params.node());
387 const AttrDomain source_domain = AttrDomain(storage.domain);
388
389 const Field<int> group_index_field = params.extract_input<Field<int>>("Group Index");
390 const GField input_field = params.extract_input<GField>("Value");
391 if (params.output_is_required("Leading")) {
392 params.set_output<GField>(
393 "Leading",
394 GField{std::make_shared<AccumulateFieldInput>(
395 source_domain, input_field, group_index_field, AccumulationMode::Leading)});
396 }
397 if (params.output_is_required("Trailing")) {
398 params.set_output<GField>(
399 "Trailing",
400 GField{std::make_shared<AccumulateFieldInput>(
401 source_domain, input_field, group_index_field, AccumulationMode::Trailing)});
402 }
403 if (params.output_is_required("Total")) {
404 params.set_output<GField>(
405 "Total",
406 GField{std::make_shared<TotalFieldInput>(source_domain, input_field, group_index_field)});
407 }
408}
409
410static void node_rna(StructRNA *srna)
411{
412 static EnumPropertyItem items[] = {
413 {CD_PROP_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Add floating point values"},
414 {CD_PROP_INT32, "INT", ICON_NODE_SOCKET_INT, "Integer", "Add integer values"},
415 {CD_PROP_FLOAT3, "FLOAT_VECTOR", ICON_NODE_SOCKET_VECTOR, "Vector", "Add 3D vector values"},
417 "TRANSFORM",
418 ICON_NODE_SOCKET_MATRIX,
419 "Transform",
420 "Multiply transformation matrices"},
421 {0, nullptr, 0, nullptr, nullptr},
422 };
423
425 "data_type",
426 "Data Type",
427 "Type of data that is accumulated",
428 items,
431
433 "domain",
434 "Domain",
435 "",
438 int(AttrDomain::Point),
439 nullptr,
440 true);
441}
442
443static void node_register()
444{
445 static blender::bke::bNodeType ntype;
446 geo_node_type_base(&ntype, "GeometryNodeAccumulateField", GEO_NODE_ACCUMULATE_FIELD);
447 ntype.ui_name = "Accumulate Field";
448 ntype.ui_description =
449 "Add the values of an evaluated field together and output the running total for each "
450 "element";
451 ntype.enum_name_legacy = "ACCUMULATE_FIELD";
454 ntype.initfunc = node_init;
456 ntype.declare = node_declare;
459 ntype, "NodeAccumulateField", node_free_standard_storage, node_copy_standard_storage);
461
462 node_rna(ntype.rna_ext.srna);
463}
464NOD_REGISTER_NODE(node_register)
465
466} // namespace blender::nodes::node_geo_accumulate_field_cc
#define NODE_CLASS_CONVERTER
Definition BKE_node.hh:453
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1240
#define GEO_NODE_ACCUMULATE_FIELD
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define final(a, b, c)
Definition BLI_hash.h:19
#define IFACE_(msgid)
@ CD_PROP_FLOAT
@ CD_PROP_FLOAT3
@ CD_PROP_INT32
@ CD_PROP_FLOAT4X4
@ SOCK_OUT
@ SOCK_INT
@ SOCK_VECTOR
@ SOCK_BOOLEAN
@ SOCK_MATRIX
@ SOCK_FLOAT
@ SOCK_ROTATION
@ SOCK_RGBA
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_storage_enum_accessors(member)
#define UI_ITEM_NONE
AttrDomain
BMesh const char void * data
long long int int64_t
unsigned long long int uint64_t
int domain_size(const AttrDomain domain) const
FieldInput(const CPPType &type, std::string debug_name="")
Definition field.cc:677
int add(GField field, GVArray *varray_ptr)
Definition field.cc:751
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:448
const CPPType & cpp_type() const
Definition FN_field.hh:632
BaseSocketDeclarationBuilder & description(std::string value="")
BaseSocketDeclarationBuilder & supports_field()
Vector< SocketDeclaration * > inputs
AccumulateFieldInput(const AttrDomain source_domain, GField input, Field< int > group_index, AccumulationMode accumulation_mode)
GVArray get_varray_for_context(const bke::GeometryFieldContext &context, const IndexMask &) const final
std::optional< AttrDomain > preferred_domain(const GeometryComponent &) const override
void for_each_field_input_recursive(FunctionRef< void(const FieldInput &)> fn) const final
std::optional< AttrDomain > preferred_domain(const GeometryComponent &) const override
TotalFieldInput(const AttrDomain source_domain, GField input, Field< int > group_index)
GVArray get_varray_for_context(const bke::GeometryFieldContext &context, const IndexMask &) const final
void for_each_field_input_recursive(FunctionRef< void(const FieldInput &)> fn) const final
#define input
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
#define T
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5414
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static void node_declare(NodeDeclarationBuilder &b)
static std::optional< eCustomDataType > node_type_from_other_socket(const bNodeSocket &socket)
static void node_init(bNodeTree *, bNode *node)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static void node_geo_exec(GeoNodeExecParams params)
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)
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:42
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:54
const EnumPropertyItem rna_enum_attribute_domain_items[]
StructRNA * srna
void * storage
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition BKE_node.hh:378
NodeDeclareFunction declare
Definition BKE_node.hh:362
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
PointerRNA * ptr
Definition wm_files.cc:4238