Blender V4.3
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
18#include "UI_interface.hh"
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 .description("An index used to group values together for multiple separate accumulations");
55
56 if (node != nullptr) {
57 const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type);
58 b.add_output(data_type, "Leading")
59 .field_source_reference_all()
60 .description(
61 "The running total of values in the corresponding group, starting at the first value");
62 b.add_output(data_type, "Trailing")
63 .field_source_reference_all()
64 .description("The running total of values in the corresponding group, starting at zero");
65 b.add_output(data_type, "Total")
66 .field_source_reference_all()
67 .description("The total of all of the values in the corresponding group");
68 }
69}
70
71static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
72{
73 uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
74 uiItemR(layout, ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
75}
76
77static void node_init(bNodeTree * /*tree*/, bNode *node)
78{
79 NodeAccumulateField *data = MEM_cnew<NodeAccumulateField>(__func__);
80 data->data_type = CD_PROP_FLOAT;
81 data->domain = int16_t(AttrDomain::Point);
82 node->storage = data;
83}
84
85enum class AccumulationMode { Leading = 0, Trailing = 1 };
86
87static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
88{
89 switch (socket.type) {
90 case SOCK_FLOAT:
91 return CD_PROP_FLOAT;
92 case SOCK_BOOLEAN:
93 case SOCK_INT:
94 return CD_PROP_INT32;
95 case SOCK_VECTOR:
96 case SOCK_RGBA:
97 case SOCK_ROTATION:
98 return CD_PROP_FLOAT3;
99 case SOCK_MATRIX:
100 return CD_PROP_FLOAT4X4;
101 default:
102 return {};
103 }
104}
105
107{
108 const NodeDeclaration &declaration = *params.node_type().static_declaration;
110
111 const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
112 if (!type) {
113 return;
114 }
115 if (params.in_out() == SOCK_OUT) {
116 params.add_item(
117 IFACE_("Leading"),
118 [type](LinkSearchOpParams &params) {
119 bNode &node = params.add_node("GeometryNodeAccumulateField");
120 node_storage(node).data_type = *type;
121 params.update_and_connect_available_socket(node, "Leading");
122 },
123 0);
124 params.add_item(
125 IFACE_("Trailing"),
126 [type](LinkSearchOpParams &params) {
127 bNode &node = params.add_node("GeometryNodeAccumulateField");
128 node_storage(node).data_type = *type;
129 params.update_and_connect_available_socket(node, "Trailing");
130 },
131 -1);
132 params.add_item(
133 IFACE_("Total"),
134 [type](LinkSearchOpParams &params) {
135 bNode &node = params.add_node("GeometryNodeAccumulateField");
136 node_storage(node).data_type = *type;
137 params.update_and_connect_available_socket(node, "Total");
138 },
139 -2);
140 }
141 else {
142 params.add_item(
143 IFACE_("Value"),
144 [type](LinkSearchOpParams &params) {
145 bNode &node = params.add_node("GeometryNodeAccumulateField");
146 node_storage(node).data_type = *type;
147 params.update_and_connect_available_socket(node, "Value");
148 },
149 0);
150 }
151}
152
153template<typename T> struct AccumulationInfo {
154 static inline const T initial_value = []() {
155 if constexpr (std::is_same_v<T, float4x4>) {
156 return float4x4::identity();
157 }
158 else {
159 return T();
160 }
161 }();
162
163 static T accumulate(const T &a, const T &b)
164 {
165 if constexpr (std::is_same_v<T, float4x4>) {
166 return a * b;
167 }
168 else {
169 return a + b;
170 }
171 }
172};
173
175 private:
176 GField input_;
177 Field<int> group_index_;
178 AttrDomain source_domain_;
179 AccumulationMode accumulation_mode_;
180
181 public:
182 AccumulateFieldInput(const AttrDomain source_domain,
183 GField input,
184 Field<int> group_index,
185 AccumulationMode accumulation_mode)
186 : bke::GeometryFieldInput(input.cpp_type(), "Accumulation"),
187 input_(input),
188 group_index_(group_index),
189 source_domain_(source_domain),
190 accumulation_mode_(accumulation_mode)
191 {
192 }
193
195 const IndexMask & /*mask*/) const final
196 {
197 const AttributeAccessor attributes = *context.attributes();
198 const int64_t domain_size = attributes.domain_size(source_domain_);
199 if (domain_size == 0) {
200 return {};
201 }
202
203 const bke::GeometryFieldContext source_context{context, source_domain_};
204 fn::FieldEvaluator evaluator{source_context, domain_size};
205 evaluator.add(input_);
206 evaluator.add(group_index_);
207 evaluator.evaluate();
208 const GVArray g_values = evaluator.get_evaluated(0);
209 const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
210
211 GVArray g_output;
212
213 bke::attribute_math::convert_to_static_type(g_values.type(), [&](auto dummy) {
214 using T = decltype(dummy);
215 if constexpr (is_same_any_v<T, int, float, float3, float4x4>) {
216 Array<T> outputs(domain_size);
217 const VArray<T> values = g_values.typed<T>();
218
219 if (group_indices.is_single()) {
220 T accumulation = AccumulationInfo<T>::initial_value;
221 if (accumulation_mode_ == AccumulationMode::Leading) {
222 for (const int i : values.index_range()) {
223 accumulation = AccumulationInfo<T>::accumulate(accumulation, values[i]);
224 outputs[i] = accumulation;
225 }
226 }
227 else {
228 for (const int i : values.index_range()) {
229 outputs[i] = accumulation;
230 accumulation = AccumulationInfo<T>::accumulate(accumulation, values[i]);
231 }
232 }
233 }
234 else {
235 Map<int, T> accumulations;
236 if (accumulation_mode_ == AccumulationMode::Leading) {
237 for (const int i : values.index_range()) {
238 T &accumulation_value = accumulations.lookup_or_add(
239 group_indices[i], AccumulationInfo<T>::initial_value);
240 accumulation_value = AccumulationInfo<T>::accumulate(accumulation_value, values[i]);
241 outputs[i] = accumulation_value;
242 }
243 }
244 else {
245 for (const int i : values.index_range()) {
246 T &accumulation_value = accumulations.lookup_or_add(
247 group_indices[i], AccumulationInfo<T>::initial_value);
248 outputs[i] = accumulation_value;
249 accumulation_value = AccumulationInfo<T>::accumulate(accumulation_value, values[i]);
250 }
251 }
252 }
253
254 g_output = VArray<T>::ForContainer(std::move(outputs));
255 }
256 });
257
258 return attributes.adapt_domain(std::move(g_output), source_domain_, context.domain());
259 }
260
261 uint64_t hash() const override
262 {
263 return get_default_hash(input_, group_index_, source_domain_, accumulation_mode_);
264 }
265
266 bool is_equal_to(const fn::FieldNode &other) const override
267 {
268 if (const AccumulateFieldInput *other_accumulate = dynamic_cast<const AccumulateFieldInput *>(
269 &other))
270 {
271 return input_ == other_accumulate->input_ &&
272 group_index_ == other_accumulate->group_index_ &&
273 source_domain_ == other_accumulate->source_domain_ &&
274 accumulation_mode_ == other_accumulate->accumulation_mode_;
275 }
276 return false;
277 }
278
279 std::optional<AttrDomain> preferred_domain(
280 const GeometryComponent & /*component*/) const override
281 {
282 return source_domain_;
283 }
284};
285
287 private:
288 GField input_;
289 Field<int> group_index_;
290 AttrDomain source_domain_;
291
292 public:
293 TotalFieldInput(const AttrDomain source_domain, GField input, Field<int> group_index)
294 : bke::GeometryFieldInput(input.cpp_type(), "Total Value"),
295 input_(input),
296 group_index_(group_index),
297 source_domain_(source_domain)
298 {
299 }
300
302 const IndexMask & /*mask*/) const final
303 {
304 const AttributeAccessor attributes = *context.attributes();
305 const int64_t domain_size = attributes.domain_size(source_domain_);
306 if (domain_size == 0) {
307 return {};
308 }
309
310 const bke::GeometryFieldContext source_context{context, source_domain_};
311 fn::FieldEvaluator evaluator{source_context, domain_size};
312 evaluator.add(input_);
313 evaluator.add(group_index_);
314 evaluator.evaluate();
315 const GVArray g_values = evaluator.get_evaluated(0);
316 const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
317
318 GVArray g_outputs;
319
320 bke::attribute_math::convert_to_static_type(g_values.type(), [&](auto dummy) {
321 using T = decltype(dummy);
322 if constexpr (is_same_any_v<T, int, float, float3, float4x4>) {
323 const VArray<T> values = g_values.typed<T>();
324 if (group_indices.is_single()) {
325 T accumulation = AccumulationInfo<T>::initial_value;
326 for (const int i : values.index_range()) {
327 accumulation = AccumulationInfo<T>::accumulate(accumulation, values[i]);
328 }
329 g_outputs = VArray<T>::ForSingle(accumulation, domain_size);
330 }
331 else {
332 Map<int, T> accumulations;
333 for (const int i : values.index_range()) {
334 T &value = accumulations.lookup_or_add(group_indices[i],
335 AccumulationInfo<T>::initial_value);
336 value = AccumulationInfo<T>::accumulate(value, values[i]);
337 }
338 Array<T> outputs(domain_size);
339 for (const int i : values.index_range()) {
340 outputs[i] = accumulations.lookup(group_indices[i]);
341 }
342 g_outputs = VArray<T>::ForContainer(std::move(outputs));
343 }
344 }
345 });
346
347 return attributes.adapt_domain(std::move(g_outputs), source_domain_, context.domain());
348 }
349
350 uint64_t hash() const override
351 {
352 return get_default_hash(input_, group_index_, source_domain_);
353 }
354
355 bool is_equal_to(const fn::FieldNode &other) const override
356 {
357 if (const TotalFieldInput *other_field = dynamic_cast<const TotalFieldInput *>(&other)) {
358 return input_ == other_field->input_ && group_index_ == other_field->group_index_ &&
359 source_domain_ == other_field->source_domain_;
360 }
361 return false;
362 }
363
364 std::optional<AttrDomain> preferred_domain(
365 const GeometryComponent & /*component*/) const override
366 {
367 return source_domain_;
368 }
369};
370
372{
373 const NodeAccumulateField &storage = node_storage(params.node());
374 const AttrDomain source_domain = AttrDomain(storage.domain);
375
376 const Field<int> group_index_field = params.extract_input<Field<int>>("Group Index");
377 const GField input_field = params.extract_input<GField>("Value");
378 if (params.output_is_required("Leading")) {
379 params.set_output<GField>(
380 "Leading",
381 GField{std::make_shared<AccumulateFieldInput>(
382 source_domain, input_field, group_index_field, AccumulationMode::Leading)});
383 }
384 if (params.output_is_required("Trailing")) {
385 params.set_output<GField>(
386 "Trailing",
387 GField{std::make_shared<AccumulateFieldInput>(
388 source_domain, input_field, group_index_field, AccumulationMode::Trailing)});
389 }
390 if (params.output_is_required("Total")) {
391 params.set_output<GField>(
392 "Total",
393 GField{std::make_shared<TotalFieldInput>(source_domain, input_field, group_index_field)});
394 }
395}
396
397static void node_rna(StructRNA *srna)
398{
399 static EnumPropertyItem items[] = {
400 {CD_PROP_FLOAT, "FLOAT", 0, "Float", "Add floating point values"},
401 {CD_PROP_INT32, "INT", 0, "Integer", "Add integer values"},
402 {CD_PROP_FLOAT3, "FLOAT_VECTOR", 0, "Vector", "Add 3D vector values"},
403 {CD_PROP_FLOAT4X4, "TRANSFORM", 0, "Transform", "Multiply transformation matrices"},
404 {0, nullptr, 0, nullptr, nullptr},
405 };
406
408 "data_type",
409 "Data Type",
410 "Type of data that is accumulated",
411 items,
414
416 "domain",
417 "Domain",
418 "",
421 int(AttrDomain::Point),
422 nullptr,
423 true);
424}
425
426static void node_register()
427{
428 static blender::bke::bNodeType ntype;
429
430 geo_node_type_base(&ntype, GEO_NODE_ACCUMULATE_FIELD, "Accumulate Field", NODE_CLASS_CONVERTER);
432 ntype.initfunc = node_init;
434 ntype.declare = node_declare;
437 &ntype, "NodeAccumulateField", node_free_standard_storage, node_copy_standard_storage);
439
440 node_rna(ntype.rna_ext.srna);
441}
442NOD_REGISTER_NODE(node_register)
443
444} // namespace blender::nodes::node_geo_accumulate_field_cc
#define NODE_CLASS_CONVERTER
Definition BKE_node.hh:410
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#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
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
int domain_size(const AttrDomain domain) const
int add(GField field, GVArray *varray_ptr)
Definition field.cc:756
const CPPType & cpp_type() const
Definition FN_field.hh:634
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
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
local_group_size(16, 16) .push_constant(Type b
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define T
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
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)
Definition BLI_hash.hh:219
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:46
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:58
const EnumPropertyItem rna_enum_attribute_domain_items[]
signed short int16_t
Definition stdint.h:76
__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
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