Blender V5.0
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
16#include "GEO_reorder.hh"
17
18#include "NOD_rna_define.hh"
19
20#include "RNA_enum_types.hh"
21
23#include "UI_resources.hh"
24
25#include "node_geometry_util.hh"
26
28
30{
31 b.use_custom_socket_order();
32 b.allow_any_socket_order();
33 b.add_default_layout();
34 b.add_input<decl::Geometry>("Geometry").description("Geometry to sort the elements of");
35 b.add_output<decl::Geometry>("Geometry").propagate_all().align_with_previous();
36 b.add_input<decl::Bool>("Selection").default_value(true).field_on_all().hide_value();
37 b.add_input<decl::Int>("Group ID").field_on_all().hide_value();
38 b.add_input<decl::Float>("Sort Weight").field_on_all().hide_value();
39}
40
41static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
42{
43 layout->prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
44}
45
46static void node_init(bNodeTree * /*tree*/, bNode *node)
47{
49}
50
51static void grouped_sort(const OffsetIndices<int> offsets,
52 const Span<float> weights,
54{
55 const auto comparator = [&](const int index_a, const int index_b) {
56 const float weight_a = weights[index_a];
57 const float weight_b = weights[index_b];
58 if (UNLIKELY(weight_a == weight_b)) {
59 /* Approach to make it stable. */
60 return index_a < index_b;
61 }
62 return weight_a < weight_b;
63 };
64
65 threading::parallel_for(offsets.index_range(), 250, [&](const IndexRange range) {
66 for (const int group_index : range) {
67 MutableSpan<int> group = indices.slice(offsets[group_index]);
68 parallel_sort(group.begin(), group.end(), comparator);
69 }
70 });
71}
72
74 MutableSpan<int> r_offsets,
75 MutableSpan<int> r_indices)
76{
78 Array<int> counts(r_offsets.size(), 0);
79
80 for (const int64_t index : indices.index_range()) {
81 const int curve_index = indices[index];
82 r_indices[r_offsets[curve_index] + counts[curve_index]] = int(index);
83 counts[curve_index]++;
84 }
85}
86
87template<typename T, typename Func>
88static void parallel_transform(MutableSpan<T> values, const int64_t grain_size, const Func &func)
89{
90 threading::parallel_for(values.index_range(), grain_size, [&](const IndexRange range) {
91 MutableSpan<T> values_range = values.slice(range);
92 std::transform(values_range.begin(), values_range.end(), values_range.begin(), func);
93 });
94}
95
96static Array<int> invert_permutation(const Span<int> permutation)
97{
98 Array<int> data(permutation.size());
99 threading::parallel_for(permutation.index_range(), 2048, [&](const IndexRange range) {
100 for (const int64_t i : range) {
101 data[permutation[i]] = i;
102 }
103 });
104 return data;
105}
106
107static int identifiers_to_indices(MutableSpan<int> r_identifiers_to_indices)
108{
109 const VectorSet<int> deduplicated_identifiers(r_identifiers_to_indices);
110 parallel_transform(r_identifiers_to_indices, 2048, [&](const int identifier) {
111 return deduplicated_identifiers.index_of(identifier);
112 });
113
114 Array<int> indices(deduplicated_identifiers.size());
116 parallel_sort(indices.begin(), indices.end(), [&](const int index_a, const int index_b) {
117 return deduplicated_identifiers[index_a] < deduplicated_identifiers[index_b];
118 });
121 r_identifiers_to_indices, 4096, [&](const int index) { return permutation[index]; });
122 return deduplicated_identifiers.size();
123}
124
125static std::optional<Array<int>> sorted_indices(const fn::FieldContext &field_context,
126 const int domain_size,
127 const Field<bool> selection_field,
128 const Field<int> group_id_field,
129 const Field<float> weight_field)
130{
131 if (domain_size == 0) {
132 return std::nullopt;
133 }
134
135 FieldEvaluator evaluator(field_context, domain_size);
136 evaluator.set_selection(selection_field);
137 evaluator.add(group_id_field);
138 evaluator.add(weight_field);
139 evaluator.evaluate();
141 const VArray<int> group_id = evaluator.get_evaluated<int>(0);
142 const VArray<float> weight = evaluator.get_evaluated<float>(1);
143
144 if (group_id.is_single() && weight.is_single()) {
145 return std::nullopt;
146 }
147 if (mask.is_empty()) {
148 return std::nullopt;
149 }
150
151 Array<int> gathered_indices(mask.size());
152
153 if (group_id.is_single()) {
154 mask.to_indices<int>(gathered_indices);
155 Array<float> weight_span(domain_size);
156 array_utils::copy(weight, mask, weight_span.as_mutable_span());
157 grouped_sort(Span({0, int(mask.size())}), weight_span, gathered_indices);
158 }
159 else {
160 Array<int> gathered_group_id(mask.size());
161 array_utils::gather(group_id, mask, gathered_group_id.as_mutable_span());
162 const int total_groups = identifiers_to_indices(gathered_group_id);
163 Array<int> offsets_to_sort(total_groups + 1, 0);
164 find_points_by_group_index(gathered_group_id, offsets_to_sort, gathered_indices);
165 if (!weight.is_single()) {
166 Array<float> weight_span(mask.size());
167 array_utils::gather(weight, mask, weight_span.as_mutable_span());
168 grouped_sort(offsets_to_sort.as_span(), weight_span, gathered_indices);
169 }
170 parallel_transform<int>(gathered_indices, 2048, [&](const int pos) { return mask[pos]; });
171 }
172
173 if (array_utils::indices_are_range(gathered_indices, IndexRange(domain_size))) {
174 return std::nullopt;
175 }
176
177 if (mask.size() == domain_size) {
178 return gathered_indices;
179 }
180
181 IndexMaskMemory memory;
182 const IndexMask unselected = mask.complement(IndexRange(domain_size), memory);
183
184 Array<int> indices(domain_size);
185
186 array_utils::scatter<int>(gathered_indices, mask, indices);
187 unselected.foreach_index_optimized<int>(GrainSize(2048),
188 [&](const int index) { indices[index] = index; });
189
190 if (array_utils::indices_are_range(indices, indices.index_range())) {
191 return std::nullopt;
192 }
193
194 return indices;
195}
196
198{
199 GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
200 const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
201 const Field<int> group_id_field = params.extract_input<Field<int>>("Group ID");
202 const Field<float> weight_field = params.extract_input<Field<float>>("Sort Weight");
203 const bke::AttrDomain domain = bke::AttrDomain(params.node().custom1);
204
205 const NodeAttributeFilter attribute_filter = params.get_attribute_filter("Geometry");
206
208
209 std::atomic<bool> has_reorder = false;
210 std::atomic<bool> has_unsupported = false;
211 if (domain == bke::AttrDomain::Instance) {
212 if (const bke::Instances *instances = geometry_set.get_instances()) {
213 if (const std::optional<Array<int>> indices = sorted_indices(
214 bke::InstancesFieldContext(*instances),
215 instances->instances_num(),
216 selection_field,
217 group_id_field,
218 weight_field))
219 {
221 *instances, *indices, attribute_filter);
222 geometry_set.replace_instances(result);
223 has_reorder = true;
224 }
225 }
226 }
227 else {
228 geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
229 for (const auto [type, domains] : geometry::components_supported_reordering().items()) {
230 const bke::GeometryComponent *src_component = geometry_set.get_component(type);
231 if (src_component == nullptr || src_component->is_empty()) {
232 continue;
233 }
234 if (!domains.contains(domain)) {
235 has_unsupported = true;
236 continue;
237 }
238 has_reorder = true;
239 const std::optional<Array<int>> indices = sorted_indices(
240 bke::GeometryFieldContext(*src_component, domain),
241 src_component->attribute_domain_size(domain),
242 selection_field,
243 group_id_field,
244 weight_field);
245 if (!indices.has_value()) {
246 continue;
247 }
249 *src_component, *indices, domain, attribute_filter);
250 geometry_set.remove(type);
251 geometry_set.add(*dst_component.get());
252 }
253 });
254 }
255
256 if (has_unsupported && !has_reorder) {
257 params.error_message_add(NodeWarningType::Info,
258 TIP_("Domain and geometry type combination is unsupported"));
259 }
260
261 params.set_output("Geometry", std::move(geometry_set));
262}
263
264template<typename T>
266 const EnumPropertyItem *src_items)
267{
269 for (const EnumPropertyItem *item = src_items; item->identifier != nullptr; item++) {
270 if (values.contains(T(item->value))) {
271 items.append(*item);
272 }
273 }
274 items.append({0, nullptr, 0, nullptr, nullptr});
275 return items;
276}
277
278static void node_rna(StructRNA *srna)
279{
280 static const Vector<EnumPropertyItem> supported_items = items_value_in<bke::AttrDomain>(
287
289 "domain",
290 "Domain",
291 "",
292 supported_items.data(),
295}
296
297static void node_register()
298{
299 static blender::bke::bNodeType ntype;
300
301 geo_node_type_base(&ntype, "GeometryNodeSortElements", GEO_NODE_SORT_ELEMENTS);
302 ntype.ui_name = "Sort Elements";
303 ntype.ui_description = "Rearrange geometry elements, changing their indices";
304 ntype.enum_name_legacy = "SORT_ELEMENTS";
306 ntype.declare = node_declare;
307 ntype.initfunc = node_init;
311
312 node_rna(ntype.rna_ext.srna);
313}
314NOD_REGISTER_NODE(node_register)
315
316} // namespace blender::nodes::node_geo_sort_elements_cc
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#define GEO_NODE_SORT_ELEMENTS
#define UNLIKELY(x)
#define TIP_(msgid)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_inline_enum_accessors(member)
#define UI_ITEM_NONE
BMesh const char void * data
long long int int64_t
Span< T > as_span() const
Definition BLI_array.hh:243
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:248
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool contains(const T &value) const
Definition BLI_span.hh:277
int64_t size() const
int64_t index_of(const Key &key) 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:383
int add(GField field, GVArray *varray_ptr)
Definition field.cc:751
IndexMask get_evaluated_selection_as_mask() const
Definition field.cc:817
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:448
void foreach_index_optimized(Fn &&fn) const
static void remember_deformed_positions_if_necessary(GeometrySet &geometry)
static ushort indices[]
uint pos
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
#define T
void copy(const GVArray &src, GMutableSpan dst, int64_t grain_size=4096)
bool indices_are_range(Span< int > indices, IndexRange range)
void scatter(const Span< T > src, const Span< IndexT > indices, MutableSpan< T > dst, const int64_t grain_size=4096)
void gather(const GVArray &src, const IndexMask &indices, GMutableSpan dst, int64_t grain_size=4096)
void fill_index_range(MutableSpan< T > span, const T start=0)
ImplicitSharingPtr< GeometryComponent > GeometryComponentPtr
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void foreach_real_geometry(bke::GeometrySet &geometry, FunctionRef< void(bke::GeometrySet &geometry_set)> fn)
const MultiValueMap< bke::GeometryComponent::Type, bke::AttrDomain > & components_supported_reordering()
Definition reorder.cc:29
bke::GeometryComponentPtr reordered_component(const bke::GeometryComponent &src_component, Span< int > old_by_new_map, bke::AttrDomain domain, const bke::AttributeFilter &attribute_filter)
Definition reorder.cc:379
bke::Instances * reorder_instaces(const bke::Instances &src_instances, Span< int > old_by_new_map, const bke::AttributeFilter &attribute_filter)
Definition reorder.cc:370
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 build_reverse_offsets(Span< int > indices, MutableSpan< int > offsets)
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:93
void parallel_sort(RandomAccessIterator begin, RandomAccessIterator end)
Definition BLI_sort.hh:23
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[]
const char * identifier
Definition RNA_types.hh:657
StructRNA * srna
int16_t custom1
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 add(const GeometryComponent &component)
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
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
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