Blender V5.0
node_geo_field_average.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"
9#include "BLI_math_vector.h"
10#include "BLI_vector.hh"
11#include "BLI_virtual_array.hh"
12
13#include "NOD_rna_define.hh"
15
16#include "RNA_enum_types.hh"
17
18#include "node_geometry_util.hh"
19
21#include "UI_resources.hh"
22
23#include <numeric>
24
26
28{
29 const bNode *node = b.node_or_null();
30
31 if (node != nullptr) {
32 const eCustomDataType data_type = eCustomDataType(node->custom1);
33 b.add_input(data_type, "Value")
34 .supports_field()
35 .description("The values the mean and median will be calculated from");
36 }
37
38 b.add_input<decl::Int>("Group ID", "Group Index")
39 .supports_field()
40 .hide_value()
41 .description("An index used to group values together for multiple separate operations");
42
43 if (node != nullptr) {
44 const eCustomDataType data_type = eCustomDataType(node->custom1);
45 b.add_output(data_type, "Mean")
46 .field_source_reference_all()
47 .description("The sum of all values in each group divided by the size of said group");
48 b.add_output(data_type, "Median")
49 .translation_context(BLT_I18NCONTEXT_ID_NODETREE)
50 .field_source_reference_all()
51 .description(
52 "The middle value in each group when all values are sorted from lowest to highest");
53 }
54}
55
56static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
57{
58 layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
59 layout->prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
60}
61
62static void node_init(bNodeTree * /*tree*/, bNode *node)
63{
64 node->custom1 = CD_PROP_FLOAT;
65 node->custom2 = int16_t(AttrDomain::Point);
66}
67
68enum class Operation { Mean = 0, Median = 1 };
69
70static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
71{
72 switch (socket.type) {
73 case SOCK_FLOAT:
74 case SOCK_BOOLEAN:
75 case SOCK_INT:
76 return CD_PROP_FLOAT;
77 case SOCK_VECTOR:
78 case SOCK_RGBA:
79 return CD_PROP_FLOAT3;
80 default:
81 return std::nullopt;
82 }
83}
84
86{
87 const NodeDeclaration &declaration = *params.node_type().static_declaration;
89
90 const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
91 if (!type) {
92 return;
93 }
94 if (params.in_out() == SOCK_OUT) {
95 params.add_item(
96 IFACE_("Mean"),
97 [type](LinkSearchOpParams &params) {
98 bNode &node = params.add_node("GeometryNodeFieldAverage");
99 node.custom1 = *type;
100 params.update_and_connect_available_socket(node, "Mean");
101 },
102 0);
103 params.add_item(
105 [type](LinkSearchOpParams &params) {
106 bNode &node = params.add_node("GeometryNodeFieldAverage");
107 node.custom1 = *type;
108 params.update_and_connect_available_socket(node, "Median");
109 },
110 -1);
111 }
112 else {
113 params.add_item(
114 IFACE_("Value"),
115 [type](LinkSearchOpParams &params) {
116 bNode &node = params.add_node("GeometryNodeFieldAverage");
117 node.custom1 = *type;
118 params.update_and_connect_available_socket(node, "Value");
119 },
120 0);
121 }
122}
123
124template<typename T> static T calculate_median(MutableSpan<T> values)
125{
126 if constexpr (std::is_same<T, float3>::value) {
127 Array<float> x_vals(values.size()), y_vals(values.size()), z_vals(values.size());
128
129 for (const int i : values.index_range()) {
130 float3 value = values[i];
131 x_vals[i] = value.x;
132 y_vals[i] = value.y;
133 z_vals[i] = value.z;
134 }
135
136 return float3(calculate_median<float>(x_vals),
139 }
140 else {
141 const auto middle_itr = values.begin() + values.size() / 2;
142 std::nth_element(values.begin(), middle_itr, values.end());
143 if (values.size() % 2 == 0) {
144 const auto left_middle_itr = std::max_element(values.begin(), middle_itr);
145 return math::midpoint<T>(*left_middle_itr, *middle_itr);
146 }
147 return *middle_itr;
148 }
149}
150
152 private:
153 GField input_;
154 Field<int> group_index_;
155 AttrDomain source_domain_;
156 Operation operation_;
157
158 public:
159 FieldAverageInput(const AttrDomain source_domain,
161 Field<int> group_index,
162 Operation operation)
163 : bke::GeometryFieldInput(input.cpp_type(), "Calculation"),
164 input_(std::move(input)),
165 group_index_(std::move(group_index)),
166 source_domain_(source_domain),
167 operation_(operation)
168 {
169 }
170
172 const IndexMask & /*mask*/) const final
173 {
174 const AttributeAccessor attributes = *context.attributes();
175 const int64_t domain_size = attributes.domain_size(source_domain_);
176 if (domain_size == 0) {
177 return {};
178 }
179
180 const bke::GeometryFieldContext source_context{context, source_domain_};
181 fn::FieldEvaluator evaluator{source_context, domain_size};
182 evaluator.add(input_);
183 evaluator.add(group_index_);
184 evaluator.evaluate();
185 const GVArray g_values = evaluator.get_evaluated(0);
186 const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
187
188 GVArray g_outputs;
189
190 bke::attribute_math::convert_to_static_type(g_values.type(), [&](auto dummy) {
191 using T = decltype(dummy);
192 if constexpr (is_same_any_v<T, int, float, float3>) {
193 const VArraySpan<T> values = g_values.typed<T>();
194
195 if (operation_ == Operation::Mean) {
196 if (group_indices.is_single()) {
197 const T mean = std::reduce(values.begin(), values.end(), T()) / domain_size;
198 g_outputs = VArray<T>::from_single(mean, domain_size);
199 }
200 else {
201 Map<int, std::pair<T, int>> sum_and_counts;
202 for (const int i : values.index_range()) {
203 auto &pair = sum_and_counts.lookup_or_add(group_indices[i], std::make_pair(T(), 0));
204 pair.first = pair.first + values[i];
205 pair.second = pair.second + 1;
206 }
207
208 Array<T> outputs(domain_size);
209 for (const int i : values.index_range()) {
210 const auto &pair = sum_and_counts.lookup(group_indices[i]);
211 outputs[i] = pair.first / pair.second;
212 }
213 g_outputs = VArray<T>::from_container(std::move(outputs));
214 }
215 }
216 else {
217 if (group_indices.is_single()) {
218 Array<T> sorted_values(values);
219 T median = calculate_median<T>(sorted_values);
220 g_outputs = VArray<T>::from_single(median, domain_size);
221 }
222 else {
223 Map<int, Vector<T>> groups;
224 for (const int i : values.index_range()) {
225 groups.lookup_or_add(group_indices[i], Vector<T>()).append(values[i]);
226 }
227
228 Map<int, T> medians;
229 for (MutableMapItem<int, Vector<T>> group : groups.items()) {
230 medians.add(group.key, calculate_median<T>(group.value));
231 }
232
233 Array<T> outputs(domain_size);
234 for (const int i : values.index_range()) {
235 outputs[i] = medians.lookup(group_indices[i]);
236 }
237 g_outputs = VArray<T>::from_container(std::move(outputs));
238 }
239 }
240 }
241 });
242
243 return attributes.adapt_domain(std::move(g_outputs), source_domain_, context.domain());
244 }
245
247 {
248 input_.node().for_each_field_input_recursive(fn);
249 group_index_.node().for_each_field_input_recursive(fn);
250 }
251
252 uint64_t hash() const override
253 {
254 return get_default_hash(input_, group_index_, source_domain_, operation_);
255 }
256
257 bool is_equal_to(const fn::FieldNode &other) const override
258 {
259 if (const FieldAverageInput *other_field = dynamic_cast<const FieldAverageInput *>(&other)) {
260 return input_ == other_field->input_ && group_index_ == other_field->group_index_ &&
261 source_domain_ == other_field->source_domain_ &&
262 operation_ == other_field->operation_;
263 }
264 return false;
265 }
266
267 std::optional<AttrDomain> preferred_domain(
268 const GeometryComponent & /*component*/) const override
269 {
270 return source_domain_;
271 }
272};
273
275{
276 const AttrDomain source_domain = AttrDomain(params.node().custom2);
277
278 const Field<int> group_index_field = params.extract_input<Field<int>>("Group Index");
279 const GField input_field = params.extract_input<GField>("Value");
280 if (params.output_is_required("Mean")) {
281 params.set_output<GField>(
282 "Mean",
283 GField{std::make_shared<FieldAverageInput>(
284 source_domain, input_field, group_index_field, Operation::Mean)});
285 }
286 if (params.output_is_required("Median")) {
287 params.set_output<GField>(
288 "Median",
289 GField{std::make_shared<FieldAverageInput>(
290 source_domain, input_field, group_index_field, Operation::Median)});
291 }
292}
293
294static void node_rna(StructRNA *srna)
295{
296 static EnumPropertyItem items[] = {
297 {CD_PROP_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Floating-point value"},
299 "FLOAT_VECTOR",
300 ICON_NODE_SOCKET_VECTOR,
301 "Vector",
302 "3D vector with floating-point values"},
303 {0, nullptr, 0, nullptr, nullptr},
304 };
305
307 "data_type",
308 "Data Type",
309 "Type of data the outputs are calculated from",
310 items,
313
315 "domain",
316 "Domain",
317 "",
320 int(AttrDomain::Point),
321 nullptr,
322 true);
323}
324
325static void node_register()
326{
327 static blender::bke::bNodeType ntype;
328
329 geo_node_type_base(&ntype, "GeometryNodeFieldAverage");
330 ntype.ui_name = "Field Average";
331 ntype.ui_description = "Calculate the mean and median of a given field";
334 ntype.initfunc = node_init;
336 ntype.declare = node_declare;
339 node_rna(ntype.rna_ext.srna);
340}
341NOD_REGISTER_NODE(node_register)
342
343} // namespace blender::nodes::node_geo_field_average_cc
#define NODE_CLASS_CONVERTER
Definition BKE_node.hh:453
#define final(a, b, c)
Definition BLI_hash.h:19
#define BLT_I18NCONTEXT_ID_NODETREE
#define CTX_IFACE_(context, msgid)
#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
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr T * end() const
Definition BLI_span.hh:548
constexpr T * begin() const
Definition BLI_span.hh:544
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
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
bool is_equal_to(const fn::FieldNode &other) const override
FieldAverageInput(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
std::optional< AttrDomain > preferred_domain(const GeometryComponent &) const override
#define input
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define T
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
T midpoint(const T &a, const T &b)
static void node_init(bNodeTree *, bNode *node)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static void node_geo_exec(GeoNodeExecParams params)
static std::optional< eCustomDataType > node_type_from_other_socket(const bNodeSocket &socket)
static void node_declare(NodeDeclarationBuilder &b)
static T calculate_median(MutableSpan< T > values)
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
VecBase< float, 3 > float3
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)
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4238