Blender V5.0
node_geo_field_variance.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 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
21#include <numeric>
22
24
26{
27 const bNode *node = b.node_or_null();
28
29 if (node != nullptr) {
30 const eCustomDataType data_type = eCustomDataType(node->custom1);
31 b.add_input(data_type, "Value")
32 .supports_field()
33 .description("The values the standard deviation and variance will be calculated from");
34 }
35
36 b.add_input<decl::Int>("Group ID", "Group Index")
37 .supports_field()
38 .hide_value()
39 .description("An index used to group values together for multiple separate operations");
40
41 if (node != nullptr) {
42 const eCustomDataType data_type = eCustomDataType(node->custom1);
43 b.add_output(data_type, "Standard Deviation")
44 .field_source_reference_all()
45 .description("The square root of the variance for each group");
46 b.add_output(data_type, "Variance")
47 .field_source_reference_all()
48 .description("The expected squared deviation from the mean for each group");
49 }
50}
51
52static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
53{
54 layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
55 layout->prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
56}
57
58static void node_init(bNodeTree * /*tree*/, bNode *node)
59{
60 node->custom1 = CD_PROP_FLOAT;
61 node->custom2 = int16_t(AttrDomain::Point);
62}
63
64enum class Operation { StdDev = 0, Variance = 1 };
65
66static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
67{
68 switch (socket.type) {
69 case SOCK_FLOAT:
70 case SOCK_BOOLEAN:
71 case SOCK_INT:
72 return CD_PROP_FLOAT;
73 case SOCK_VECTOR:
74 case SOCK_RGBA:
75 return CD_PROP_FLOAT3;
76 default:
77 return std::nullopt;
78 }
79}
80
82{
83 const NodeDeclaration &declaration = *params.node_type().static_declaration;
85
86 const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
87 if (!type) {
88 return;
89 }
90 if (params.in_out() == SOCK_OUT) {
91 params.add_item(
92 IFACE_("Standard Deviation"),
93 [type](LinkSearchOpParams &params) {
94 bNode &node = params.add_node("GeometryNodeFieldVariance");
95 node.custom1 = *type;
96 params.update_and_connect_available_socket(node, "Standard Deviation");
97 },
98 0);
99 params.add_item(
100 IFACE_("Variance"),
101 [type](LinkSearchOpParams &params) {
102 bNode &node = params.add_node("GeometryNodeFieldVariance");
103 node.custom1 = *type;
104 params.update_and_connect_available_socket(node, "Variance");
105 },
106 -1);
107 }
108 else {
109 params.add_item(
110 IFACE_("Value"),
111 [type](LinkSearchOpParams &params) {
112 bNode &node = params.add_node("GeometryNodeFieldVariance");
113 node.custom1 = *type;
114 params.update_and_connect_available_socket(node, "Value");
115 },
116 0);
117 }
118}
119
121 private:
122 GField input_;
123 Field<int> group_index_;
124 AttrDomain source_domain_;
125 Operation operation_;
126
127 public:
128 FieldVarianceInput(const AttrDomain source_domain,
130 Field<int> group_index,
131 Operation operation)
132 : bke::GeometryFieldInput(input.cpp_type(), "Calculation"),
133 input_(std::move(input)),
134 group_index_(std::move(group_index)),
135 source_domain_(source_domain),
136 operation_(operation)
137 {
138 }
139
141 const IndexMask & /*mask*/) const final
142 {
143 const AttributeAccessor attributes = *context.attributes();
144 const int64_t domain_size = attributes.domain_size(source_domain_);
145 if (domain_size == 0) {
146 return {};
147 }
148
149 const bke::GeometryFieldContext source_context{context, source_domain_};
150 fn::FieldEvaluator evaluator{source_context, domain_size};
151 evaluator.add(input_);
152 evaluator.add(group_index_);
153 evaluator.evaluate();
154 const GVArray g_values = evaluator.get_evaluated(0);
155 const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
156
157 GVArray g_outputs;
158
159 bke::attribute_math::convert_to_static_type(g_values.type(), [&](auto dummy) {
160 using T = decltype(dummy);
161 if constexpr (is_same_any_v<T, int, float, float3>) {
162 const VArraySpan<T> values = g_values.typed<T>();
163
164 if (operation_ == Operation::StdDev) {
165 if (group_indices.is_single()) {
166 const T mean = std::reduce(values.begin(), values.end(), T()) / domain_size;
167 const T sum_of_squared_diffs = std::reduce(
168 values.begin(), values.end(), T(), [mean](T accumulator, const T &value) {
169 T difference = mean - value;
170 return accumulator + difference * difference;
171 });
172 g_outputs = VArray<T>::from_single(math::sqrt(sum_of_squared_diffs / domain_size),
173 domain_size);
174 }
175 else {
176 Map<int, std::pair<T, int>> sum_and_counts;
177 Map<int, T> deviations;
178
179 for (const int i : values.index_range()) {
180 auto &pair = sum_and_counts.lookup_or_add(group_indices[i], std::make_pair(T(), 0));
181 pair.first = pair.first + values[i];
182 pair.second = pair.second + 1;
183 }
184
185 for (const int i : values.index_range()) {
186 const auto &pair = sum_and_counts.lookup(group_indices[i]);
187 T mean = pair.first / pair.second;
188 T deviation = (mean - values[i]);
189 deviation = deviation * deviation;
190
191 T &dev_sum = deviations.lookup_or_add(group_indices[i], T());
192 dev_sum = dev_sum + deviation;
193 }
194
195 Array<T> outputs(domain_size);
196 for (const int i : values.index_range()) {
197 const auto &pair = sum_and_counts.lookup(group_indices[i]);
198 outputs[i] = math::sqrt(deviations.lookup(group_indices[i]) / pair.second);
199 }
200 g_outputs = VArray<T>::from_container(std::move(outputs));
201 }
202 }
203 else {
204 if (group_indices.is_single()) {
205 const T mean = std::reduce(values.begin(), values.end(), T()) / domain_size;
206 const T sum_of_squared_diffs = std::reduce(
207 values.begin(), values.end(), T(), [mean](T accumulator, const T &value) {
208 T difference = mean - value;
209 return accumulator + difference * difference;
210 });
211 g_outputs = VArray<T>::from_single(sum_of_squared_diffs / domain_size, domain_size);
212 }
213 else {
214 Map<int, std::pair<T, int>> sum_and_counts;
215 Map<int, T> deviations;
216
217 for (const int i : values.index_range()) {
218 auto &pair = sum_and_counts.lookup_or_add(group_indices[i], std::make_pair(T(), 0));
219 pair.first = pair.first + values[i];
220 pair.second = pair.second + 1;
221 }
222
223 for (const int i : values.index_range()) {
224 const auto &pair = sum_and_counts.lookup(group_indices[i]);
225 T mean = pair.first / pair.second;
226 T deviation = (mean - values[i]);
227 deviation = deviation * deviation;
228
229 T &dev_sum = deviations.lookup_or_add(group_indices[i], T());
230 dev_sum = dev_sum + deviation;
231 }
232
233 Array<T> outputs(domain_size);
234 for (const int i : values.index_range()) {
235 const auto &pair = sum_and_counts.lookup(group_indices[i]);
236 outputs[i] = deviations.lookup(group_indices[i]) / pair.second;
237 }
238 g_outputs = VArray<T>::from_container(std::move(outputs));
239 }
240 }
241 }
242 });
243
244 return attributes.adapt_domain(std::move(g_outputs), source_domain_, context.domain());
245 }
246
248 {
249 input_.node().for_each_field_input_recursive(fn);
250 group_index_.node().for_each_field_input_recursive(fn);
251 }
252
253 uint64_t hash() const override
254 {
255 return get_default_hash(input_, group_index_, source_domain_, operation_);
256 }
257
258 bool is_equal_to(const fn::FieldNode &other) const override
259 {
260 if (const FieldVarianceInput *other_field = dynamic_cast<const FieldVarianceInput *>(&other)) {
261 return input_ == other_field->input_ && group_index_ == other_field->group_index_ &&
262 source_domain_ == other_field->source_domain_ &&
263 operation_ == other_field->operation_;
264 }
265 return false;
266 }
267
268 std::optional<AttrDomain> preferred_domain(
269 const GeometryComponent & /*component*/) const override
270 {
271 return source_domain_;
272 }
273};
274
276{
277 const AttrDomain source_domain = AttrDomain(params.node().custom2);
278
279 const Field<int> group_index_field = params.extract_input<Field<int>>("Group Index");
280 const GField input_field = params.extract_input<GField>("Value");
281 if (params.output_is_required("Standard Deviation")) {
282 params.set_output<GField>(
283 "Standard Deviation",
284 GField{std::make_shared<FieldVarianceInput>(
285 source_domain, input_field, group_index_field, Operation::StdDev)});
286 }
287 if (params.output_is_required("Variance")) {
288 params.set_output<GField>(
289 "Variance",
290 GField{std::make_shared<FieldVarianceInput>(
291 source_domain, input_field, group_index_field, Operation::Variance)});
292 }
293}
294
295static void node_rna(StructRNA *srna)
296{
297 static EnumPropertyItem items[] = {
298 {CD_PROP_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Floating-point value"},
300 "FLOAT_VECTOR",
301 ICON_NODE_SOCKET_VECTOR,
302 "Vector",
303 "3D vector with floating-point values"},
304 {0, nullptr, 0, nullptr, nullptr},
305 };
306
308 "data_type",
309 "Data Type",
310 "Type of data the outputs are calculated from",
311 items,
314
316 "domain",
317 "Domain",
318 "",
321 int(AttrDomain::Point),
322 nullptr,
323 true);
324}
325
326static void node_register()
327{
328 static blender::bke::bNodeType ntype;
329
330 geo_node_type_base(&ntype, "GeometryNodeFieldVariance");
331 ntype.ui_name = "Field Variance";
332 ntype.ui_description = "Calculate the standard deviation and variance of a given field";
335 ntype.initfunc = node_init;
337 ntype.declare = node_declare;
340 node_rna(ntype.rna_ext.srna);
341}
342NOD_REGISTER_NODE(node_register)
343
344} // namespace blender::nodes::node_geo_field_variance_cc
#define NODE_CLASS_CONVERTER
Definition BKE_node.hh:453
#define final(a, b, c)
Definition BLI_hash.h:19
#define IFACE_(msgid)
@ CD_PROP_FLOAT
@ CD_PROP_FLOAT3
@ SOCK_OUT
@ SOCK_INT
@ SOCK_VECTOR
@ SOCK_BOOLEAN
@ SOCK_FLOAT
@ SOCK_RGBA
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_inline_enum_accessors(member)
#define UI_ITEM_NONE
AttrDomain
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
Vector< SocketDeclaration * > inputs
void for_each_field_input_recursive(FunctionRef< void(const FieldInput &)> fn) const final
std::optional< AttrDomain > preferred_domain(const GeometryComponent &) const override
bool is_equal_to(const fn::FieldNode &other) const override
FieldVarianceInput(const AttrDomain source_domain, GField input, Field< int > group_index, Operation operation)
GVArray get_varray_for_context(const bke::GeometryFieldContext &context, const IndexMask &) const final
#define input
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
static void node_init(bNodeTree *, bNode *node)
static void node_declare(NodeDeclarationBuilder &b)
static void node_geo_exec(GeoNodeExecParams params)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static std::optional< eCustomDataType > node_type_from_other_socket(const bNodeSocket &socket)
static void node_gather_link_searches(GatherLinkSearchOpParams &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)
const EnumPropertyItem rna_enum_attribute_domain_items[]
StructRNA * srna
int16_t custom1
int16_t custom2
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
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