Blender V4.3
node_geo_sort_elements.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <atomic>
6
7#include "BKE_attribute.hh"
8#include "BKE_instances.hh"
9
10#include "BLI_array_utils.hh"
11#include "BLI_index_mask.hh"
12#include "BLI_sort.hh"
13#include "BLI_task.hh"
14
15#include "GEO_reorder.hh"
16
17#include "NOD_rna_define.hh"
18
19#include "RNA_enum_types.hh"
20
21#include "UI_interface.hh"
22#include "UI_resources.hh"
23
24#include "node_geometry_util.hh"
25
27
29{
30 b.add_input<decl::Geometry>("Geometry");
31 b.add_input<decl::Bool>("Selection").default_value(true).field_on_all().hide_value();
32 b.add_input<decl::Int>("Group ID").field_on_all().hide_value();
33 b.add_input<decl::Float>("Sort Weight").field_on_all().hide_value();
34
35 b.add_output<decl::Geometry>("Geometry").propagate_all();
36}
37
38static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
39{
40 uiItemR(layout, ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
41}
42
43static void node_init(bNodeTree * /*tree*/, bNode *node)
44{
45 node->custom1 = int(bke::AttrDomain::Point);
46}
47
48static void grouped_sort(const OffsetIndices<int> offsets,
49 const Span<float> weights,
50 MutableSpan<int> indices)
51{
52 const auto comparator = [&](const int index_a, const int index_b) {
53 const float weight_a = weights[index_a];
54 const float weight_b = weights[index_b];
55 if (UNLIKELY(weight_a == weight_b)) {
56 /* Approach to make it stable. */
57 return index_a < index_b;
58 }
59 return weight_a < weight_b;
60 };
61
62 threading::parallel_for(offsets.index_range(), 250, [&](const IndexRange range) {
63 for (const int group_index : range) {
64 MutableSpan<int> group = indices.slice(offsets[group_index]);
65 parallel_sort(group.begin(), group.end(), comparator);
66 }
67 });
68}
69
70static void find_points_by_group_index(const Span<int> indices,
71 MutableSpan<int> r_offsets,
72 MutableSpan<int> r_indices)
73{
74 offset_indices::build_reverse_offsets(indices, r_offsets);
75 Array<int> counts(r_offsets.size(), 0);
76
77 for (const int64_t index : indices.index_range()) {
78 const int curve_index = indices[index];
79 r_indices[r_offsets[curve_index] + counts[curve_index]] = int(index);
80 counts[curve_index]++;
81 }
82}
83
84template<typename T, typename Func>
85static void parallel_transform(MutableSpan<T> values, const int64_t grain_size, const Func &func)
86{
87 threading::parallel_for(values.index_range(), grain_size, [&](const IndexRange range) {
88 MutableSpan<T> values_range = values.slice(range);
89 std::transform(values_range.begin(), values_range.end(), values_range.begin(), func);
90 });
91}
92
93static Array<int> invert_permutation(const Span<int> permutation)
94{
95 Array<int> data(permutation.size());
96 threading::parallel_for(permutation.index_range(), 2048, [&](const IndexRange range) {
97 for (const int64_t i : range) {
98 data[permutation[i]] = i;
99 }
100 });
101 return data;
102}
103
104static int identifiers_to_indices(MutableSpan<int> r_identifiers_to_indices)
105{
106 const VectorSet<int> deduplicated_identifiers(r_identifiers_to_indices);
107 parallel_transform(r_identifiers_to_indices, 2048, [&](const int identifier) {
108 return deduplicated_identifiers.index_of(identifier);
109 });
110
111 Array<int> indices(deduplicated_identifiers.size());
112 array_utils::fill_index_range<int>(indices);
113 parallel_sort(indices.begin(), indices.end(), [&](const int index_a, const int index_b) {
114 return deduplicated_identifiers[index_a] < deduplicated_identifiers[index_b];
115 });
116 Array<int> permutation = invert_permutation(indices);
118 r_identifiers_to_indices, 4096, [&](const int index) { return permutation[index]; });
119 return deduplicated_identifiers.size();
120}
121
122static std::optional<Array<int>> sorted_indices(const fn::FieldContext &field_context,
123 const int domain_size,
124 const Field<bool> selection_field,
125 const Field<int> group_id_field,
126 const Field<float> weight_field)
127{
128 if (domain_size == 0) {
129 return std::nullopt;
130 }
131
132 FieldEvaluator evaluator(field_context, domain_size);
133 evaluator.set_selection(selection_field);
134 evaluator.add(group_id_field);
135 evaluator.add(weight_field);
136 evaluator.evaluate();
137 const IndexMask mask = evaluator.get_evaluated_selection_as_mask();
138 const VArray<int> group_id = evaluator.get_evaluated<int>(0);
139 const VArray<float> weight = evaluator.get_evaluated<float>(1);
140
141 if (group_id.is_single() && weight.is_single()) {
142 return std::nullopt;
143 }
144 if (mask.is_empty()) {
145 return std::nullopt;
146 }
147
148 Array<int> gathered_indices(mask.size());
149
150 if (group_id.is_single()) {
151 mask.to_indices<int>(gathered_indices);
152 Array<float> weight_span(domain_size);
153 array_utils::copy(weight, mask, weight_span.as_mutable_span());
154 grouped_sort(Span({0, int(mask.size())}), weight_span, gathered_indices);
155 }
156 else {
157 Array<int> gathered_group_id(mask.size());
158 array_utils::gather(group_id, mask, gathered_group_id.as_mutable_span());
159 const int total_groups = identifiers_to_indices(gathered_group_id);
160 Array<int> offsets_to_sort(total_groups + 1, 0);
161 find_points_by_group_index(gathered_group_id, offsets_to_sort, gathered_indices);
162 if (!weight.is_single()) {
163 Array<float> weight_span(mask.size());
164 array_utils::gather(weight, mask, weight_span.as_mutable_span());
165 grouped_sort(offsets_to_sort.as_span(), weight_span, gathered_indices);
166 }
167 parallel_transform<int>(gathered_indices, 2048, [&](const int pos) { return mask[pos]; });
168 }
169
170 if (array_utils::indices_are_range(gathered_indices, IndexRange(domain_size))) {
171 return std::nullopt;
172 }
173
174 if (mask.size() == domain_size) {
175 return gathered_indices;
176 }
177
178 IndexMaskMemory memory;
179 const IndexMask unselected = mask.complement(IndexRange(domain_size), memory);
180
181 Array<int> indices(domain_size);
182
183 array_utils::scatter<int>(gathered_indices, mask, indices);
184 unselected.foreach_index_optimized<int>(GrainSize(2048),
185 [&](const int index) { indices[index] = index; });
186
187 if (array_utils::indices_are_range(indices, indices.index_range())) {
188 return std::nullopt;
189 }
190
191 return indices;
192}
193
195{
196 GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
197 const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
198 const Field<int> group_id_field = params.extract_input<Field<int>>("Group ID");
199 const Field<float> weight_field = params.extract_input<Field<float>>("Sort Weight");
200 const bke::AttrDomain domain = bke::AttrDomain(params.node().custom1);
201
202 const NodeAttributeFilter attribute_filter = params.get_attribute_filter("Geometry");
203
204 GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set);
205
206 std::atomic<bool> has_reorder = false;
207 std::atomic<bool> has_unsupported = false;
208 if (domain == bke::AttrDomain::Instance) {
209 if (const bke::Instances *instances = geometry_set.get_instances()) {
210 if (const std::optional<Array<int>> indices = sorted_indices(
211 bke::InstancesFieldContext(*instances),
212 instances->instances_num(),
213 selection_field,
214 group_id_field,
215 weight_field))
216 {
217 bke::Instances *result = geometry::reorder_instaces(
218 *instances, *indices, attribute_filter);
219 geometry_set.replace_instances(result);
220 has_reorder = true;
221 }
222 }
223 }
224 else {
225 geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
226 for (const auto [type, domains] : geometry::components_supported_reordering().items()) {
227 const bke::GeometryComponent *src_component = geometry_set.get_component(type);
228 if (src_component == nullptr || src_component->is_empty()) {
229 continue;
230 }
231 if (!domains.contains(domain)) {
232 has_unsupported = true;
233 continue;
234 }
235 has_reorder = true;
236 const std::optional<Array<int>> indices = sorted_indices(
237 bke::GeometryFieldContext(*src_component, domain),
238 src_component->attribute_domain_size(domain),
239 selection_field,
240 group_id_field,
241 weight_field);
242 if (!indices.has_value()) {
243 continue;
244 }
245 bke::GeometryComponentPtr dst_component = geometry::reordered_component(
246 *src_component, *indices, domain, attribute_filter);
247 geometry_set.remove(type);
248 geometry_set.add(*dst_component.get());
249 }
250 });
251 }
252
253 if (has_unsupported && !has_reorder) {
254 params.error_message_add(NodeWarningType::Info,
255 TIP_("Domain and geometry type combination is unsupported"));
256 }
257
258 params.set_output("Geometry", std::move(geometry_set));
259}
260
261template<typename T>
263 const EnumPropertyItem *src_items)
264{
266 for (const EnumPropertyItem *item = src_items; item->identifier != nullptr; item++) {
267 if (values.contains(T(item->value))) {
268 items.append(*item);
269 }
270 }
271 items.append({0, nullptr, 0, nullptr, nullptr});
272 return items;
273}
274
275static void node_rna(StructRNA *srna)
276{
277 static const Vector<EnumPropertyItem> supported_items = items_value_in<bke::AttrDomain>(
278 {bke::AttrDomain::Point,
279 bke::AttrDomain::Edge,
280 bke::AttrDomain::Face,
281 bke::AttrDomain::Curve,
282 bke::AttrDomain::Instance},
284
286 "domain",
287 "Domain",
288 "",
289 supported_items.data(),
291 int(bke::AttrDomain::Point));
292}
293
294static void node_register()
295{
296 static blender::bke::bNodeType ntype;
297
298 geo_node_type_base(&ntype, GEO_NODE_SORT_ELEMENTS, "Sort Elements", NODE_CLASS_GEOMETRY);
299 ntype.declare = node_declare;
300 ntype.initfunc = node_init;
304
305 node_rna(ntype.rna_ext.srna);
306}
307NOD_REGISTER_NODE(node_register)
308
309} // namespace blender::nodes::node_geo_sort_elements_cc
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:418
#define UNLIKELY(x)
#define TIP_(msgid)
#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)
Span< T > as_span() const
Definition BLI_array.hh:232
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
int64_t index_of(const Key &key) const
int64_t size() const
void append(const T &value)
int attribute_domain_size(AttrDomain domain) const
virtual bool is_empty() const
void set_selection(Field< bool > selection)
Definition FN_field.hh:385
int add(GField field, GVArray *varray_ptr)
Definition field.cc:756
IndexMask get_evaluated_selection_as_mask() const
Definition field.cc:822
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:450
void foreach_index_optimized(Fn &&fn) const
IndexMask complement(const IndexMask &universe, IndexMaskMemory &memory) const
local_group_size(16, 16) .push_constant(Type b
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
static ushort indices[]
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
static void node_declare(NodeDeclarationBuilder &b)
static int identifiers_to_indices(MutableSpan< int > r_identifiers_to_indices)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static void parallel_transform(MutableSpan< T > values, const int64_t grain_size, const Func &func)
static void node_geo_exec(GeoNodeExecParams params)
static Vector< EnumPropertyItem > items_value_in(const Span< T > values, const EnumPropertyItem *src_items)
static Array< int > invert_permutation(const Span< int > permutation)
static std::optional< Array< int > > sorted_indices(const fn::FieldContext &field_context, const int domain_size, const Field< bool > selection_field, const Field< int > group_id_field, const Field< float > weight_field)
static void grouped_sort(const OffsetIndices< int > offsets, const Span< float > weights, MutableSpan< int > indices)
static void find_points_by_group_index(const Span< int > indices, MutableSpan< int > r_offsets, MutableSpan< int > r_indices)
static void node_init(bNodeTree *, bNode *node)
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)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
void parallel_sort(RandomAccessIterator begin, RandomAccessIterator end)
Definition BLI_sort.hh:23
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
const EnumPropertyItem rna_enum_attribute_domain_items[]
__int64 int64_t
Definition stdint.h:89
const char * identifier
Definition RNA_types.hh:506
StructRNA * srna
Definition RNA_types.hh:780
void replace_instances(Instances *instances, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
const GeometryComponent * get_component(GeometryComponent::Type component_type) const
const Instances * get_instances() const
void remove(const GeometryComponent::Type component_type)
void modify_geometry_sets(ForeachSubGeometryCallback callback)
void add(const GeometryComponent &component)
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
NodeDeclareFunction declare
Definition BKE_node.hh:347
PointerRNA * ptr
Definition wm_files.cc:4126