Blender V5.0
node_geo_field_min_and_max.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
22
24{
25 const bNode *node = b.node_or_null();
26
27 if (node != nullptr) {
28 const eCustomDataType data_type = eCustomDataType(node->custom1);
29 b.add_input(data_type, "Value")
30 .supports_field()
31 .description("The values the minimum and maximum will be calculated from");
32 }
33
34 b.add_input<decl::Int>("Group ID", "Group Index")
35 .supports_field()
36 .hide_value()
37 .description("An index used to group values together for multiple separate operations");
38
39 if (node != nullptr) {
40 const eCustomDataType data_type = eCustomDataType(node->custom1);
41 b.add_output(data_type, "Min")
42 .field_source_reference_all()
43 .description("The lowest value in each group");
44 b.add_output(data_type, "Max")
45 .field_source_reference_all()
46 .description("The highest value in each group");
47 }
48}
49
50static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
51{
52 layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
53 layout->prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
54}
55
56static void node_init(bNodeTree * /*tree*/, bNode *node)
57{
58 node->custom1 = CD_PROP_FLOAT;
59 node->custom2 = int16_t(AttrDomain::Point);
60}
61
62enum class Operation { Min = 0, Max = 1 };
63
64static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
65{
66 switch (socket.type) {
67 case SOCK_FLOAT:
68 return CD_PROP_FLOAT;
69 case SOCK_BOOLEAN:
70 case SOCK_INT:
71 return CD_PROP_INT32;
72 case SOCK_VECTOR:
73 case SOCK_RGBA:
74 case SOCK_ROTATION:
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_("Min"),
93 [type](LinkSearchOpParams &params) {
94 bNode &node = params.add_node("GeometryNodeFieldMinAndMax");
95 node.custom1 = *type;
96 params.update_and_connect_available_socket(node, "Min");
97 },
98 0);
99 params.add_item(
100 IFACE_("Max"),
101 [type](LinkSearchOpParams &params) {
102 bNode &node = params.add_node("GeometryNodeFieldMinAndMax");
103 node.custom1 = *type;
104 params.update_and_connect_available_socket(node, "Max");
105 },
106 -1);
107 }
108 else {
109 params.add_item(
110 IFACE_("Value"),
111 [type](LinkSearchOpParams &params) {
112 bNode &node = params.add_node("GeometryNodeFieldMinAndMax");
113 node.custom1 = *type;
114 params.update_and_connect_available_socket(node, "Value");
115 },
116 0);
117 }
118}
119
120template<typename T> struct MinMaxInfo {
121 static inline const T min_initial_value = []() {
122 if constexpr (std::is_same_v<T, float3>) {
123 return float3(std::numeric_limits<float>::max());
124 }
125 else {
126 return std::numeric_limits<T>::max();
127 }
128 }();
129
130 static inline const T max_initial_value = []() {
131 if constexpr (std::is_same_v<T, float3>) {
132 return float3(std::numeric_limits<float>::lowest());
133 }
134 else {
135 return std::numeric_limits<T>::lowest();
136 }
137 }();
138};
139
141 private:
142 GField input_;
143 Field<int> group_index_;
144 AttrDomain source_domain_;
145 Operation operation_;
146
147 public:
148 FieldMinMaxInput(const AttrDomain source_domain,
150 Field<int> group_index,
151 Operation operation)
152 : bke::GeometryFieldInput(input.cpp_type(), "Calculation"),
153 input_(std::move(input)),
154 group_index_(std::move(group_index)),
155 source_domain_(source_domain),
156 operation_(operation)
157 {
158 }
159
161 const IndexMask & /*mask*/) const final
162 {
163 const AttributeAccessor attributes = *context.attributes();
164 const int64_t domain_size = attributes.domain_size(source_domain_);
165 if (domain_size == 0) {
166 return {};
167 }
168
169 const bke::GeometryFieldContext source_context{context, source_domain_};
170 fn::FieldEvaluator evaluator{source_context, domain_size};
171 evaluator.add(input_);
172 evaluator.add(group_index_);
173 evaluator.evaluate();
174 const GVArray g_values = evaluator.get_evaluated(0);
175 const VArray<int> group_indices = evaluator.get_evaluated<int>(1);
176
177 GVArray g_outputs;
178
179 bke::attribute_math::convert_to_static_type(g_values.type(), [&](auto dummy) {
180 using T = decltype(dummy);
181 if constexpr (is_same_any_v<T, int, float, float3>) {
182 const VArray<T> values = g_values.typed<T>();
183
184 if (operation_ == Operation::Min) {
185 if (group_indices.is_single()) {
186 T result = MinMaxInfo<T>::min_initial_value;
187 for (const int i : values.index_range()) {
188 result = math::min(result, values[i]);
189 }
190 g_outputs = VArray<T>::from_single(result, domain_size);
191 }
192 else {
193 Map<int, T> results;
194 for (const int i : values.index_range()) {
195 T &value = results.lookup_or_add(group_indices[i], MinMaxInfo<T>::min_initial_value);
196 value = math::min(value, values[i]);
197 }
198 Array<T> outputs(domain_size);
199 for (const int i : values.index_range()) {
200 outputs[i] = results.lookup(group_indices[i]);
201 }
202 g_outputs = VArray<T>::from_container(std::move(outputs));
203 }
204 }
205 else {
206 if (group_indices.is_single()) {
207 T result = MinMaxInfo<T>::max_initial_value;
208 for (const int i : values.index_range()) {
209 result = math::max(result, values[i]);
210 }
211 g_outputs = VArray<T>::from_single(result, domain_size);
212 }
213 else {
214 Map<int, T> results;
215 for (const int i : values.index_range()) {
216 T &value = results.lookup_or_add(group_indices[i], MinMaxInfo<T>::max_initial_value);
217 value = math::max(value, values[i]);
218 }
219 Array<T> outputs(domain_size);
220 for (const int i : values.index_range()) {
221 outputs[i] = results.lookup(group_indices[i]);
222 }
223 g_outputs = VArray<T>::from_container(std::move(outputs));
224 }
225 }
226 }
227 });
228
229 return attributes.adapt_domain(std::move(g_outputs), source_domain_, context.domain());
230 }
231
233 {
234 input_.node().for_each_field_input_recursive(fn);
235 group_index_.node().for_each_field_input_recursive(fn);
236 }
237
238 uint64_t hash() const override
239 {
240 return get_default_hash(input_, group_index_, source_domain_, operation_);
241 }
242
243 bool is_equal_to(const fn::FieldNode &other) const override
244 {
245 if (const FieldMinMaxInput *other_field = dynamic_cast<const FieldMinMaxInput *>(&other)) {
246 return input_ == other_field->input_ && group_index_ == other_field->group_index_ &&
247 source_domain_ == other_field->source_domain_ &&
248 operation_ == other_field->operation_;
249 }
250 return false;
251 }
252
253 std::optional<AttrDomain> preferred_domain(
254 const GeometryComponent & /*component*/) const override
255 {
256 return source_domain_;
257 }
258};
259
261{
262 const AttrDomain source_domain = AttrDomain(params.node().custom2);
263
264 const Field<int> group_index_field = params.extract_input<Field<int>>("Group Index");
265 const GField input_field = params.extract_input<GField>("Value");
266 if (params.output_is_required("Min")) {
267 params.set_output<GField>("Min",
268 GField{std::make_shared<FieldMinMaxInput>(
269 source_domain, input_field, group_index_field, Operation::Min)});
270 }
271 if (params.output_is_required("Max")) {
272 params.set_output<GField>("Max",
273 GField{std::make_shared<FieldMinMaxInput>(
274 source_domain, input_field, group_index_field, Operation::Max)});
275 }
276}
277
278static void node_rna(StructRNA *srna)
279{
280 static EnumPropertyItem items[] = {
281 {CD_PROP_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Floating-point value"},
282 {CD_PROP_INT32, "INT", ICON_NODE_SOCKET_INT, "Integer", "32-bit integer"},
284 "FLOAT_VECTOR",
285 ICON_NODE_SOCKET_VECTOR,
286 "Vector",
287 "3D vector with floating-point values"},
288 {0, nullptr, 0, nullptr, nullptr},
289 };
290
292 "data_type",
293 "Data Type",
294 "Type of data the outputs are calculated from",
295 items,
298
300 "domain",
301 "Domain",
302 "",
305 int(AttrDomain::Point),
306 nullptr,
307 true);
308}
309
310static void node_register()
311{
312 static blender::bke::bNodeType ntype;
313
314 geo_node_type_base(&ntype, "GeometryNodeFieldMinAndMax");
315 ntype.ui_name = "Field Min & Max";
316 ntype.ui_description = "Calculate the minimum and maximum of a given field";
319 ntype.initfunc = node_init;
321 ntype.declare = node_declare;
324 node_rna(ntype.rna_ext.srna);
325}
326NOD_REGISTER_NODE(node_register)
327
328} // namespace blender::nodes::node_geo_field_min_and_max_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
@ CD_PROP_INT32
@ SOCK_OUT
@ 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
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
GVArray get_varray_for_context(const bke::GeometryFieldContext &context, const IndexMask &) const final
FieldMinMaxInput(const AttrDomain source_domain, GField input, Field< int > group_index, Operation operation)
void for_each_field_input_recursive(FunctionRef< void(const FieldInput &)> fn) 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
static std::optional< eCustomDataType > node_type_from_other_socket(const bNodeSocket &socket)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static void node_declare(NodeDeclarationBuilder &b)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
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
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)
PointerRNA * ptr
Definition wm_files.cc:4238