Blender V5.0
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")
18 .structure_type(StructureType::Field);
19 b.add_input<decl::Int>("Group ID").supports_field().hide_value();
20
21 b.add_output<decl::Int>("Index").field_source_reference_all().description(
22 "Index of nearest element");
23 b.add_output<decl::Bool>("Has Neighbor").field_source_reference_all();
24}
25
26static KDTree_3d *build_kdtree(const Span<float3> positions, const IndexMask &mask)
27{
28 KDTree_3d *tree = BLI_kdtree_3d_new(mask.size());
29 mask.foreach_index(
30 [&](const int index) { BLI_kdtree_3d_insert(tree, index, positions[index]); });
31 BLI_kdtree_3d_balance(tree);
32 return tree;
33}
34
35static int find_nearest_non_self(const KDTree_3d &tree, const float3 &position, const int index)
36{
37 return BLI_kdtree_3d_find_nearest_cb_cpp(
38 &tree,
39 position,
40 nullptr,
41 [index](const int other, const float * /*co*/, const float /*dist_sq*/) {
42 return index == other ? 0 : 1;
43 });
44}
45
46static void find_neighbors(const KDTree_3d &tree,
47 const Span<float3> positions,
48 const IndexMask &mask,
49 MutableSpan<int> r_indices)
50{
51 mask.foreach_index(GrainSize(1024), [&](const int index) {
52 r_indices[index] = find_nearest_non_self(tree, positions[index], index);
53 });
54}
55
57 private:
58 const Field<float3> positions_field_;
59 const Field<int> group_field_;
60
61 public:
63 : bke::GeometryFieldInput(CPPType::get<int>(), "Index of Nearest"),
64 positions_field_(std::move(positions_field)),
65 group_field_(std::move(group_field))
66 {
67 }
68
70 const IndexMask &mask) const final
71 {
72 if (!context.attributes()) {
73 return {};
74 }
75 const int domain_size = context.attributes()->domain_size(context.domain());
76 fn::FieldEvaluator evaluator{context, domain_size};
77 evaluator.add(positions_field_);
78 evaluator.add(group_field_);
79 evaluator.evaluate();
80 const VArraySpan<float3> positions = evaluator.get_evaluated<float3>(0);
81 const VArray<int> group_ids = evaluator.get_evaluated<int>(1);
82
84
85 if (group_ids.is_single()) {
86 result.reinitialize(mask.min_array_size());
87 KDTree_3d *tree = build_kdtree(positions, IndexRange(domain_size));
88 find_neighbors(*tree, positions, mask, result);
89 BLI_kdtree_3d_free(tree);
90 return VArray<int>::from_container(std::move(result));
91 }
92 const VArraySpan<int> group_ids_span(group_ids);
93
94 const VectorSet<int> group_indexing(group_ids_span);
95 const int groups_num = group_indexing.size();
96
97 IndexMaskMemory mask_memory;
98 Array<IndexMask> all_indices_by_group_id(groups_num);
99 Array<IndexMask> lookup_indices_by_group_id(groups_num);
100
101 const auto get_group_index = [&](const int i) {
102 const int group_id = group_ids_span[i];
103 return group_indexing.index_of(group_id);
104 };
105
107 IndexMask(domain_size), mask_memory, get_group_index, all_indices_by_group_id);
108
109 if (mask.size() == domain_size) {
110 lookup_indices_by_group_id = all_indices_by_group_id;
111 result.reinitialize(domain_size);
112 }
113 else {
114 IndexMask::from_groups<int>(mask, mask_memory, get_group_index, lookup_indices_by_group_id);
115 result.reinitialize(mask.min_array_size());
116 }
117
118 /* The grain size should be larger as each tree gets smaller. */
119 const int avg_tree_size = domain_size / group_indexing.size();
120 const int grain_size = std::max(8192 / avg_tree_size, 1);
121 threading::parallel_for(IndexRange(groups_num), grain_size, [&](const IndexRange range) {
122 for (const int group_index : range) {
123 const IndexMask &tree_mask = all_indices_by_group_id[group_index];
124 const IndexMask &lookup_mask = lookup_indices_by_group_id[group_index];
125 KDTree_3d *tree = build_kdtree(positions, tree_mask);
126 find_neighbors(*tree, positions, lookup_mask, result);
127 BLI_kdtree_3d_free(tree);
128 }
129 });
130
131 return VArray<int>::from_container(std::move(result));
132 }
133
134 void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
135 {
136 positions_field_.node().for_each_field_input_recursive(fn);
137 group_field_.node().for_each_field_input_recursive(fn);
138 }
139
141 {
142 return get_default_hash(positions_field_, group_field_);
143 }
144
145 bool is_equal_to(const fn::FieldNode &other) const final
146 {
147 if (const auto *other_field = dynamic_cast<const IndexOfNearestFieldInput *>(&other)) {
148 return positions_field_ == other_field->positions_field_ &&
149 group_field_ == other_field->group_field_;
150 }
151 return false;
152 }
153
154 std::optional<AttrDomain> preferred_domain(const GeometryComponent &component) const final
155 {
156 return bke::try_detect_field_domain(component, positions_field_);
157 }
158};
159
161 private:
162 const Field<int> group_field_;
163
164 public:
166 : bke::GeometryFieldInput(CPPType::get<bool>(), "Has Neighbor"),
167 group_field_(std::move(group_field))
168 {
169 }
170
172 const IndexMask &mask) const final
173 {
174 if (!context.attributes()) {
175 return {};
176 }
177 const int domain_size = context.attributes()->domain_size(context.domain());
178 if (domain_size == 1) {
179 return VArray<bool>::from_single(false, mask.min_array_size());
180 }
181
182 fn::FieldEvaluator evaluator{context, domain_size};
183 evaluator.add(group_field_);
184 evaluator.evaluate();
185 const VArray<int> group = evaluator.get_evaluated<int>(0);
186
187 if (group.is_single()) {
188 return VArray<bool>::from_single(true, mask.min_array_size());
189 }
190
191 Map<int, int> counts;
192 const VArraySpan<int> group_span(group);
193 mask.foreach_index([&](const int i) {
194 counts.add_or_modify(
195 group_span[i], [](int *count) { *count = 1; }, [](int *count) { (*count)++; });
196 });
197 Array<bool> result(mask.min_array_size());
198 mask.foreach_index([&](const int i) { result[i] = counts.lookup(group_span[i]) > 1; });
199 return VArray<bool>::from_container(std::move(result));
200 }
201
202 void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
203 {
204 group_field_.node().for_each_field_input_recursive(fn);
205 }
206
208 {
209 return get_default_hash(39847876, group_field_);
210 }
211
212 bool is_equal_to(const fn::FieldNode &other) const final
213 {
214 if (const auto *other_field = dynamic_cast<const HasNeighborFieldInput *>(&other)) {
215 return group_field_ == other_field->group_field_;
216 }
217 return false;
218 }
219
220 std::optional<AttrDomain> preferred_domain(const GeometryComponent &component) const final
221 {
222 return bke::try_detect_field_domain(component, group_field_);
223 }
224};
225
227{
228 Field<float3> position_field = params.extract_input<Field<float3>>("Position");
229 Field<int> group_field = params.extract_input<Field<int>>("Group ID");
230
231 if (params.output_is_required("Index")) {
232 params.set_output("Index",
233 Field<int>(std::make_shared<IndexOfNearestFieldInput>(
234 std::move(position_field), group_field)));
235 }
236
237 if (params.output_is_required("Has Neighbor")) {
238 params.set_output(
239 "Has Neighbor",
240 Field<bool>(std::make_shared<HasNeighborFieldInput>(std::move(group_field))));
241 }
242}
243
244static void node_register()
245{
246 static blender::bke::bNodeType ntype;
247
248 geo_node_type_base(&ntype, "GeometryNodeIndexOfNearest", GEO_NODE_INDEX_OF_NEAREST);
249 ntype.ui_name = "Index of Nearest";
250 ntype.ui_description =
251 "Find the nearest element in a group. Similar to the \"Sample Nearest\" node";
252 ntype.enum_name_legacy = "INDEX_OF_NEAREST";
255 ntype.declare = node_declare;
257}
259
260} // namespace blender::nodes::node_geo_index_of_nearest_cc
#define NODE_CLASS_CONVERTER
Definition BKE_node.hh:453
#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 from_single(T value, const int64_t size)
static VArray from_container(ContainerT container)
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:2416
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:238
std::string ui_description
Definition BKE_node.hh:244
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
i
Definition text_draw.cc:230