Blender V4.5
node_geo_index_of_nearest.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_array.hh"
6#include "BLI_kdtree.h"
7#include "BLI_map.hh"
8#include "BLI_task.hh"
9
10#include "node_geometry_util.hh"
11
13
15{
16 b.add_input<decl::Vector>("Position").implicit_field(NODE_DEFAULT_INPUT_POSITION_FIELD);
17 b.add_input<decl::Int>("Group ID").supports_field().hide_value();
18
19 b.add_output<decl::Int>("Index").field_source_reference_all().description(
20 "Index of nearest element");
21 b.add_output<decl::Bool>("Has Neighbor").field_source_reference_all();
22}
23
24static KDTree_3d *build_kdtree(const Span<float3> positions, const IndexMask &mask)
25{
26 KDTree_3d *tree = BLI_kdtree_3d_new(mask.size());
27 mask.foreach_index(
28 [&](const int index) { BLI_kdtree_3d_insert(tree, index, positions[index]); });
29 BLI_kdtree_3d_balance(tree);
30 return tree;
31}
32
33static int find_nearest_non_self(const KDTree_3d &tree, const float3 &position, const int index)
34{
35 return BLI_kdtree_3d_find_nearest_cb_cpp(
36 &tree,
37 position,
38 nullptr,
39 [index](const int other, const float * /*co*/, const float /*dist_sq*/) {
40 return index == other ? 0 : 1;
41 });
42}
43
44static void find_neighbors(const KDTree_3d &tree,
45 const Span<float3> positions,
46 const IndexMask &mask,
47 MutableSpan<int> r_indices)
48{
49 mask.foreach_index(GrainSize(1024), [&](const int index) {
50 r_indices[index] = find_nearest_non_self(tree, positions[index], index);
51 });
52}
53
55 private:
56 const Field<float3> positions_field_;
57 const Field<int> group_field_;
58
59 public:
61 : bke::GeometryFieldInput(CPPType::get<int>(), "Index of Nearest"),
62 positions_field_(std::move(positions_field)),
63 group_field_(std::move(group_field))
64 {
65 }
66
68 const IndexMask &mask) const final
69 {
70 if (!context.attributes()) {
71 return {};
72 }
73 const int domain_size = context.attributes()->domain_size(context.domain());
74 fn::FieldEvaluator evaluator{context, domain_size};
75 evaluator.add(positions_field_);
76 evaluator.add(group_field_);
77 evaluator.evaluate();
78 const VArraySpan<float3> positions = evaluator.get_evaluated<float3>(0);
79 const VArray<int> group_ids = evaluator.get_evaluated<int>(1);
80
82
83 if (group_ids.is_single()) {
84 result.reinitialize(mask.min_array_size());
85 KDTree_3d *tree = build_kdtree(positions, IndexRange(domain_size));
86 find_neighbors(*tree, positions, mask, result);
87 BLI_kdtree_3d_free(tree);
88 return VArray<int>::ForContainer(std::move(result));
89 }
90 const VArraySpan<int> group_ids_span(group_ids);
91
92 const VectorSet<int> group_indexing(group_ids_span);
93 const int groups_num = group_indexing.size();
94
95 IndexMaskMemory mask_memory;
96 Array<IndexMask> all_indices_by_group_id(groups_num);
97 Array<IndexMask> lookup_indices_by_group_id(groups_num);
98
99 const auto get_group_index = [&](const int i) {
100 const int group_id = group_ids_span[i];
101 return group_indexing.index_of(group_id);
102 };
103
105 IndexMask(domain_size), mask_memory, get_group_index, all_indices_by_group_id);
106
107 if (mask.size() == domain_size) {
108 lookup_indices_by_group_id = all_indices_by_group_id;
109 result.reinitialize(domain_size);
110 }
111 else {
112 IndexMask::from_groups<int>(mask, mask_memory, get_group_index, lookup_indices_by_group_id);
113 result.reinitialize(mask.min_array_size());
114 }
115
116 /* The grain size should be larger as each tree gets smaller. */
117 const int avg_tree_size = domain_size / group_indexing.size();
118 const int grain_size = std::max(8192 / avg_tree_size, 1);
119 threading::parallel_for(IndexRange(groups_num), grain_size, [&](const IndexRange range) {
120 for (const int group_index : range) {
121 const IndexMask &tree_mask = all_indices_by_group_id[group_index];
122 const IndexMask &lookup_mask = lookup_indices_by_group_id[group_index];
123 KDTree_3d *tree = build_kdtree(positions, tree_mask);
124 find_neighbors(*tree, positions, lookup_mask, result);
125 BLI_kdtree_3d_free(tree);
126 }
127 });
128
129 return VArray<int>::ForContainer(std::move(result));
130 }
131
132 void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
133 {
134 positions_field_.node().for_each_field_input_recursive(fn);
135 group_field_.node().for_each_field_input_recursive(fn);
136 }
137
139 {
140 return get_default_hash(positions_field_, group_field_);
141 }
142
143 bool is_equal_to(const fn::FieldNode &other) const final
144 {
145 if (const auto *other_field = dynamic_cast<const IndexOfNearestFieldInput *>(&other)) {
146 return positions_field_ == other_field->positions_field_ &&
147 group_field_ == other_field->group_field_;
148 }
149 return false;
150 }
151
152 std::optional<AttrDomain> preferred_domain(const GeometryComponent &component) const final
153 {
154 return bke::try_detect_field_domain(component, positions_field_);
155 }
156};
157
159 private:
160 const Field<int> group_field_;
161
162 public:
164 : bke::GeometryFieldInput(CPPType::get<bool>(), "Has Neighbor"),
165 group_field_(std::move(group_field))
166 {
167 }
168
170 const IndexMask &mask) const final
171 {
172 if (!context.attributes()) {
173 return {};
174 }
175 const int domain_size = context.attributes()->domain_size(context.domain());
176 if (domain_size == 1) {
177 return VArray<bool>::ForSingle(false, mask.min_array_size());
178 }
179
180 fn::FieldEvaluator evaluator{context, domain_size};
181 evaluator.add(group_field_);
182 evaluator.evaluate();
183 const VArray<int> group = evaluator.get_evaluated<int>(0);
184
185 if (group.is_single()) {
186 return VArray<bool>::ForSingle(true, mask.min_array_size());
187 }
188
189 Map<int, int> counts;
190 const VArraySpan<int> group_span(group);
191 mask.foreach_index([&](const int i) {
192 counts.add_or_modify(
193 group_span[i], [](int *count) { *count = 1; }, [](int *count) { (*count)++; });
194 });
195 Array<bool> result(mask.min_array_size());
196 mask.foreach_index([&](const int i) { result[i] = counts.lookup(group_span[i]) > 1; });
197 return VArray<bool>::ForContainer(std::move(result));
198 }
199
200 void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
201 {
202 group_field_.node().for_each_field_input_recursive(fn);
203 }
204
206 {
207 return get_default_hash(39847876, group_field_);
208 }
209
210 bool is_equal_to(const fn::FieldNode &other) const final
211 {
212 if (const auto *other_field = dynamic_cast<const HasNeighborFieldInput *>(&other)) {
213 return group_field_ == other_field->group_field_;
214 }
215 return false;
216 }
217
218 std::optional<AttrDomain> preferred_domain(const GeometryComponent &component) const final
219 {
220 return bke::try_detect_field_domain(component, group_field_);
221 }
222};
223
225{
226 Field<float3> position_field = params.extract_input<Field<float3>>("Position");
227 Field<int> group_field = params.extract_input<Field<int>>("Group ID");
228
229 if (params.output_is_required("Index")) {
230 params.set_output("Index",
231 Field<int>(std::make_shared<IndexOfNearestFieldInput>(
232 std::move(position_field), group_field)));
233 }
234
235 if (params.output_is_required("Has Neighbor")) {
236 params.set_output(
237 "Has Neighbor",
238 Field<bool>(std::make_shared<HasNeighborFieldInput>(std::move(group_field))));
239 }
240}
241
242static void node_register()
243{
244 static blender::bke::bNodeType ntype;
245
246 geo_node_type_base(&ntype, "GeometryNodeIndexOfNearest", GEO_NODE_INDEX_OF_NEAREST);
247 ntype.ui_name = "Index of Nearest";
248 ntype.ui_description =
249 "Find the nearest element in a group. Similar to the \"Sample Nearest\" node";
250 ntype.enum_name_legacy = "INDEX_OF_NEAREST";
253 ntype.declare = node_declare;
255}
257
258} // namespace blender::nodes::node_geo_index_of_nearest_cc
#define NODE_CLASS_CONVERTER
Definition BKE_node.hh:439
#define GEO_NODE_INDEX_OF_NEAREST
#define final(a, b, c)
Definition BLI_hash.h:19
A KD-tree for nearest neighbor search.
@ NODE_DEFAULT_INPUT_POSITION_FIELD
#define NOD_REGISTER_NODE(REGISTER_FUNC)
unsigned long long int uint64_t
static void from_groups(const IndexMask &universe, IndexMaskMemory &memory, Fn &&get_group_index, MutableSpan< IndexMask > r_masks)
const Value & lookup(const Key &key) const
Definition BLI_map.hh:545
auto add_or_modify(const Key &key, const CreateValueF &create_value, const ModifyValueF &modify_value) -> decltype(create_value(nullptr))
Definition BLI_map.hh:481
static VArray ForContainer(ContainerT container)
static VArray ForSingle(T value, const int64_t size)
int64_t size() const
int64_t index_of(const Key &key) 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
GVArray get_varray_for_context(const bke::GeometryFieldContext &context, const IndexMask &mask) const final
std::optional< AttrDomain > preferred_domain(const GeometryComponent &component) const final
void for_each_field_input_recursive(FunctionRef< void(const FieldInput &)> fn) const override
IndexOfNearestFieldInput(Field< float3 > positions_field, Field< int > group_field)
GVArray get_varray_for_context(const bke::GeometryFieldContext &context, const IndexMask &mask) const final
void for_each_field_input_recursive(FunctionRef< void(const FieldInput &)> fn) const override
std::optional< AttrDomain > preferred_domain(const GeometryComponent &component) const final
KDTree_3d * tree
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
int count
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
std::optional< AttrDomain > try_detect_field_domain(const GeometryComponent &component, const fn::GField &field)
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
static void find_neighbors(const KDTree_3d &tree, const Span< float3 > positions, const IndexMask &mask, MutableSpan< int > r_indices)
static void node_geo_exec(GeoNodeExecParams params)
static void node_declare(NodeDeclarationBuilder &b)
static int find_nearest_non_self(const KDTree_3d &tree, const float3 &position, const int index)
static KDTree_3d * build_kdtree(const Span< float3 > positions, const IndexMask &mask)
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
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)
Defines a node type.
Definition BKE_node.hh:226
std::string ui_description
Definition BKE_node.hh:232
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:347
const char * enum_name_legacy
Definition BKE_node.hh:235
NodeDeclareFunction declare
Definition BKE_node.hh:355
i
Definition text_draw.cc:230