Blender V4.3
node_geo_attribute_statistic.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 <algorithm>
6#include <numeric>
7
8#include "NOD_rna_define.hh"
9
10#include "UI_interface.hh"
11#include "UI_resources.hh"
12
13#include "BLI_array_utils.hh"
14
16
17#include "RNA_enum_types.hh"
18
19#include "node_geometry_util.hh"
20
22
24{
25 const bNode *node = b.node_or_null();
26
27 b.add_input<decl::Geometry>("Geometry");
28 b.add_input<decl::Bool>("Selection").default_value(true).field_on_all().hide_value();
29
30 if (node != nullptr) {
31 const eCustomDataType data_type = eCustomDataType(node->custom1);
32 b.add_input(data_type, "Attribute").hide_value().field_on_all();
33
34 b.add_output(data_type, N_("Mean"));
35 b.add_output(data_type, N_("Median"));
36 b.add_output(data_type, N_("Sum"));
37 b.add_output(data_type, N_("Min"));
38 b.add_output(data_type, N_("Max"));
39 b.add_output(data_type, N_("Range"));
40 b.add_output(data_type, N_("Standard Deviation"));
41 b.add_output(data_type, N_("Variance"));
42 }
43}
44
45static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
46{
47 uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
48 uiItemR(layout, ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
49}
50
51static void node_init(bNodeTree * /*tree*/, bNode *node)
52{
53 node->custom1 = CD_PROP_FLOAT;
54 node->custom2 = int16_t(AttrDomain::Point);
55}
56
57static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
58{
59 switch (socket.type) {
60 case SOCK_FLOAT:
61 case SOCK_BOOLEAN:
62 case SOCK_INT:
63 return CD_PROP_FLOAT;
64 case SOCK_VECTOR:
65 case SOCK_RGBA:
66 case SOCK_ROTATION:
67 return CD_PROP_FLOAT3;
68 default:
69 return {};
70 }
71}
72
74{
75 const blender::bke::bNodeType &node_type = params.node_type();
76 const NodeDeclaration &declaration = *params.node_type().static_declaration;
78
79 const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
80 if (!type) {
81 return;
82 }
83
84 if (params.in_out() == SOCK_IN) {
85 params.add_item(IFACE_("Attribute"), [node_type, type](LinkSearchOpParams &params) {
86 bNode &node = params.add_node(node_type);
87 node.custom1 = *type;
88 params.update_and_connect_available_socket(node, "Attribute");
89 });
90 }
91 else {
92 for (const StringRefNull name :
93 {"Mean", "Median", "Sum", "Min", "Max", "Range", "Standard Deviation", "Variance"})
94 {
95 params.add_item(IFACE_(name.c_str()), [node_type, name, type](LinkSearchOpParams &params) {
96 bNode &node = params.add_node(node_type);
97 node.custom1 = *type;
98 params.update_and_connect_available_socket(node, name);
99 });
100 }
101 }
102}
103
104template<typename T> static T compute_sum(const Span<T> data)
105{
106 return std::accumulate(data.begin(), data.end(), T());
107}
108
109static float compute_variance(const Span<float> data, const float mean)
110{
111 if (data.size() <= 1) {
112 return 0.0f;
113 }
114
115 float sum_of_squared_differences = std::accumulate(
116 data.begin(), data.end(), 0.0f, [mean](float accumulator, float value) {
117 float difference = mean - value;
118 return accumulator + difference * difference;
119 });
120
121 return sum_of_squared_differences / data.size();
122}
123
124static float median_of_sorted_span(const Span<float> data)
125{
126 if (data.is_empty()) {
127 return 0.0f;
128 }
129
130 const float median = data[data.size() / 2];
131
132 /* For spans of even length, the median is the average of the middle two elements. */
133 if (data.size() % 2 == 0) {
134 return (median + data[data.size() / 2 - 1]) * 0.5f;
135 }
136 return median;
137}
138
140{
141 GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry");
142 const bNode &node = params.node();
143 const eCustomDataType data_type = eCustomDataType(node.custom1);
144 const AttrDomain domain = AttrDomain(node.custom2);
145 Vector<const GeometryComponent *> components = geometry_set.get_components();
146
147 const Field<bool> selection_field = params.get_input<Field<bool>>("Selection");
148
149 switch (data_type) {
150 case CD_PROP_FLOAT: {
151 const Field<float> input_field = params.get_input<Field<float>>("Attribute");
153 for (const GeometryComponent *component : components) {
154 const std::optional<AttributeAccessor> attributes = component->attributes();
155 if (!attributes.has_value()) {
156 continue;
157 }
158 if (attributes->domain_supported(domain)) {
159 const bke::GeometryFieldContext field_context{*component, domain};
160 fn::FieldEvaluator data_evaluator{field_context, attributes->domain_size(domain)};
161 data_evaluator.add(input_field);
162 data_evaluator.set_selection(selection_field);
163 data_evaluator.evaluate();
164 const VArray<float> component_data = data_evaluator.get_evaluated<float>(0);
165 const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask();
166
167 const int next_data_index = data.size();
168 data.resize(next_data_index + selection.size());
169 MutableSpan<float> selected_data = data.as_mutable_span().slice(next_data_index,
170 selection.size());
171 array_utils::gather(component_data, selection, selected_data);
172 }
173 }
174
175 float mean = 0.0f;
176 float median = 0.0f;
177 float sum = 0.0f;
178 float min = 0.0f;
179 float max = 0.0f;
180 float range = 0.0f;
181 float standard_deviation = 0.0f;
182 float variance = 0.0f;
183 const bool sort_required = params.output_is_required("Min") ||
184 params.output_is_required("Max") ||
185 params.output_is_required("Range") ||
186 params.output_is_required("Median");
187 const bool sum_required = params.output_is_required("Sum") ||
188 params.output_is_required("Mean");
189 const bool variance_required = params.output_is_required("Standard Deviation") ||
190 params.output_is_required("Variance");
191
192 if (data.size() != 0) {
193 if (sort_required) {
194 std::sort(data.begin(), data.end());
195 median = median_of_sorted_span(data);
196
197 min = data.first();
198 max = data.last();
199 range = max - min;
200 }
201 if (sum_required || variance_required) {
202 sum = compute_sum<float>(data);
203 mean = sum / data.size();
204
205 if (variance_required) {
206 variance = compute_variance(data, mean);
207 standard_deviation = std::sqrt(variance);
208 }
209 }
210 }
211
212 if (sum_required) {
213 params.set_output("Sum", sum);
214 params.set_output("Mean", mean);
215 }
216 if (sort_required) {
217 params.set_output("Min", min);
218 params.set_output("Max", max);
219 params.set_output("Range", range);
220 params.set_output("Median", median);
221 }
222 if (variance_required) {
223 params.set_output("Standard Deviation", standard_deviation);
224 params.set_output("Variance", variance);
225 }
226 break;
227 }
228 case CD_PROP_FLOAT3: {
229 const Field<float3> input_field = params.get_input<Field<float3>>("Attribute");
231 for (const GeometryComponent *component : components) {
232 const std::optional<AttributeAccessor> attributes = component->attributes();
233 if (!attributes.has_value()) {
234 continue;
235 }
236 if (attributes->domain_supported(domain)) {
237 const bke::GeometryFieldContext field_context{*component, domain};
238 fn::FieldEvaluator data_evaluator{field_context, attributes->domain_size(domain)};
239 data_evaluator.add(input_field);
240 data_evaluator.set_selection(selection_field);
241 data_evaluator.evaluate();
242 const VArray<float3> component_data = data_evaluator.get_evaluated<float3>(0);
243 const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask();
244
245 const int next_data_index = data.size();
246 data.resize(data.size() + selection.size());
247 MutableSpan<float3> selected_data = data.as_mutable_span().slice(next_data_index,
248 selection.size());
249 array_utils::gather(component_data, selection, selected_data);
250 }
251 }
252
253 float3 median{0};
254 float3 min{0};
255 float3 max{0};
256 float3 range{0};
257 float3 sum{0};
258 float3 mean{0};
259 float3 variance{0};
260 float3 standard_deviation{0};
261 const bool sort_required = params.output_is_required("Min") ||
262 params.output_is_required("Max") ||
263 params.output_is_required("Range") ||
264 params.output_is_required("Median");
265 const bool sum_required = params.output_is_required("Sum") ||
266 params.output_is_required("Mean");
267 const bool variance_required = params.output_is_required("Standard Deviation") ||
268 params.output_is_required("Variance");
269
270 Array<float> data_x;
271 Array<float> data_y;
272 Array<float> data_z;
273 if (sort_required || variance_required) {
274 data_x.reinitialize(data.size());
275 data_y.reinitialize(data.size());
276 data_z.reinitialize(data.size());
277 for (const int i : data.index_range()) {
278 data_x[i] = data[i].x;
279 data_y[i] = data[i].y;
280 data_z[i] = data[i].z;
281 }
282 }
283
284 if (data.size() != 0) {
285 if (sort_required) {
286 std::sort(data_x.begin(), data_x.end());
287 std::sort(data_y.begin(), data_y.end());
288 std::sort(data_z.begin(), data_z.end());
289
290 const float x_median = median_of_sorted_span(data_x);
291 const float y_median = median_of_sorted_span(data_y);
292 const float z_median = median_of_sorted_span(data_z);
293 median = float3(x_median, y_median, z_median);
294
295 min = float3(data_x.first(), data_y.first(), data_z.first());
296 max = float3(data_x.last(), data_y.last(), data_z.last());
297 range = max - min;
298 }
299 if (sum_required || variance_required) {
300 sum = compute_sum(data.as_span());
301 mean = sum / data.size();
302
303 if (variance_required) {
304 const float x_variance = compute_variance(data_x, mean.x);
305 const float y_variance = compute_variance(data_y, mean.y);
306 const float z_variance = compute_variance(data_z, mean.z);
307 variance = float3(x_variance, y_variance, z_variance);
308 standard_deviation = float3(
309 std::sqrt(variance.x), std::sqrt(variance.y), std::sqrt(variance.z));
310 }
311 }
312 }
313
314 if (sum_required) {
315 params.set_output("Sum", sum);
316 params.set_output("Mean", mean);
317 }
318 if (sort_required) {
319 params.set_output("Min", min);
320 params.set_output("Max", max);
321 params.set_output("Range", range);
322 params.set_output("Median", median);
323 }
324 if (variance_required) {
325 params.set_output("Standard Deviation", standard_deviation);
326 params.set_output("Variance", variance);
327 }
328 break;
329 }
330 default:
331 break;
332 }
333}
334
335static void node_rna(StructRNA *srna)
336{
338 srna,
339 "data_type",
340 "Data Type",
341 "The data type the attribute is converted to before calculating the results",
345 [](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
346 *r_free = true;
348 return ELEM(item.value, CD_PROP_FLOAT, CD_PROP_FLOAT3);
349 });
350 });
351
353 "domain",
354 "Domain",
355 "Which domain to read the data from",
358 int(AttrDomain::Point),
359 nullptr,
360 true);
361}
362
363static void node_register()
364{
365 static blender::bke::bNodeType ntype;
366
368 &ntype, GEO_NODE_ATTRIBUTE_STATISTIC, "Attribute Statistic", NODE_CLASS_ATTRIBUTE);
369
370 ntype.initfunc = node_init;
371 ntype.declare = node_declare;
376
377 node_rna(ntype.rna_ext.srna);
378}
380
381} // namespace blender::nodes::node_geo_attribute_statistic_cc
#define NODE_CLASS_ATTRIBUTE
Definition BKE_node.hh:419
#define ELEM(...)
#define IFACE_(msgid)
@ CD_PROP_FLOAT
@ CD_PROP_FLOAT3
@ SOCK_IN
@ SOCK_INT
@ SOCK_VECTOR
@ SOCK_BOOLEAN
@ SOCK_FLOAT
@ SOCK_ROTATION
@ SOCK_RGBA
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_inline_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)
static T sum(const btAlignedObjectArray< T > &items)
const T * end() const
Definition BLI_array.hh:314
const T & last(const int64_t n=0) const
Definition BLI_array.hh:285
const T & first() const
Definition BLI_array.hh:270
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:388
const T * begin() const
Definition BLI_array.hh:310
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
int add(GField field, GVArray *varray_ptr)
Definition field.cc:756
Vector< SocketDeclaration * > inputs
local_group_size(16, 16) .push_constant(Type b
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void gather(const GVArray &src, const IndexMask &indices, GMutableSpan dst, int64_t grain_size=4096)
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
static float median_of_sorted_span(const Span< float > data)
static std::optional< eCustomDataType > node_type_from_other_socket(const bNodeSocket &socket)
static float compute_variance(const Span< float > data, const float mean)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
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)
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
const EnumPropertyItem rna_enum_attribute_domain_items[]
const EnumPropertyItem rna_enum_attribute_type_items[]
#define min(a, b)
Definition sort.c:32
signed short int16_t
Definition stdint.h:76
StructRNA * srna
Definition RNA_types.hh:780
Vector< const GeometryComponent * > get_components() const
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
#define N_(msgid)
PointerRNA * ptr
Definition wm_files.cc:4126