Blender V4.5
node_geo_sample_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 "DNA_mesh_types.h"
7
8#include "BLI_math_vector.hh"
9
10#include "BKE_bvhutils.hh"
11
12#include "NOD_rna_define.hh"
13
14#include "UI_interface.hh"
15#include "UI_resources.hh"
16
17#include "RNA_enum_types.hh"
18
19#include "node_geometry_util.hh"
20
21namespace blender::nodes {
22
24 const VArray<float3> &positions,
25 const IndexMask &mask,
26 const MutableSpan<int> r_indices,
27 const MutableSpan<float> r_distances_sq,
28 const MutableSpan<float3> r_positions)
29{
30 BLI_assert(positions.size() >= r_indices.size());
31 BLI_assert(positions.size() >= r_distances_sq.size());
32 BLI_assert(positions.size() >= r_positions.size());
33
34 mask.foreach_index([&](const int i) {
35 BVHTreeNearest nearest;
36 nearest.index = -1;
37 nearest.dist_sq = FLT_MAX;
38 const float3 position = positions[i];
40 tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data);
41 if (!r_indices.is_empty()) {
42 r_indices[i] = nearest.index;
43 }
44 if (!r_distances_sq.is_empty()) {
45 r_distances_sq[i] = nearest.dist_sq;
46 }
47 if (!r_positions.is_empty()) {
48 r_positions[i] = nearest.co;
49 }
50 });
51}
52
53} // namespace blender::nodes
54
56
58{
59 b.add_input<decl::Geometry>("Geometry")
60 .supported_type({GeometryComponent::Type::Mesh, GeometryComponent::Type::PointCloud});
61 b.add_input<decl::Vector>("Sample Position").implicit_field(NODE_DEFAULT_INPUT_POSITION_FIELD);
62 b.add_output<decl::Int>("Index").dependent_field({1});
63}
64
65static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
66{
67 layout->prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
68}
69
70static void node_init(bNodeTree * /*tree*/, bNode *node)
71{
72 node->custom1 = CD_PROP_FLOAT;
73 node->custom2 = int(AttrDomain::Point);
74}
75
77 const VArray<float3> &positions,
78 const IndexMask &mask,
79 MutableSpan<int> r_indices,
80 MutableSpan<float> r_distances_sq)
81{
82 BLI_assert(positions.size() >= r_indices.size());
83 if (tree_data.tree == nullptr) {
84 r_indices.fill(0);
85 r_distances_sq.fill(0.0f);
86 return;
87 }
88
89 mask.foreach_index([&](const int i) {
90 BVHTreeNearest nearest;
91 nearest.index = -1;
92 nearest.dist_sq = FLT_MAX;
93 const float3 position = positions[i];
95 position,
96 &nearest,
97 tree_data.nearest_callback,
98 &const_cast<bke::BVHTreeFromPointCloud &>(tree_data));
99 r_indices[i] = nearest.index;
100 if (!r_distances_sq.is_empty()) {
101 r_distances_sq[i] = nearest.dist_sq;
102 }
103 });
104}
105
106static void get_closest_mesh_points(const Mesh &mesh,
107 const VArray<float3> &positions,
108 const IndexMask &mask,
109 const MutableSpan<int> r_point_indices,
110 const MutableSpan<float> r_distances_sq,
111 const MutableSpan<float3> r_positions)
112{
113 BLI_assert(mesh.verts_num > 0);
114 bke::BVHTreeFromMesh tree_data = mesh.bvh_verts();
115 get_closest_in_bvhtree(tree_data, positions, mask, r_point_indices, r_distances_sq, r_positions);
116}
117
118static void get_closest_mesh_edges(const Mesh &mesh,
119 const VArray<float3> &positions,
120 const IndexMask &mask,
121 const MutableSpan<int> r_edge_indices,
122 const MutableSpan<float> r_distances_sq,
123 const MutableSpan<float3> r_positions)
124{
125 BLI_assert(mesh.edges_num > 0);
126 bke::BVHTreeFromMesh tree_data = mesh.bvh_edges();
127 get_closest_in_bvhtree(tree_data, positions, mask, r_edge_indices, r_distances_sq, r_positions);
128}
129
130static void get_closest_mesh_tris(const Mesh &mesh,
131 const VArray<float3> &positions,
132 const IndexMask &mask,
133 const MutableSpan<int> r_tri_indices,
134 const MutableSpan<float> r_distances_sq,
135 const MutableSpan<float3> r_positions)
136{
137 BLI_assert(mesh.faces_num > 0);
138 bke::BVHTreeFromMesh tree_data = mesh.bvh_corner_tris();
139 get_closest_in_bvhtree(tree_data, positions, mask, r_tri_indices, r_distances_sq, r_positions);
140}
141
142static void get_closest_mesh_faces(const Mesh &mesh,
143 const VArray<float3> &positions,
144 const IndexMask &mask,
145 const MutableSpan<int> r_face_indices,
146 const MutableSpan<float> r_distances_sq,
147 const MutableSpan<float3> r_positions)
148{
149 BLI_assert(mesh.faces_num > 0);
150
151 Array<int> tri_indices(positions.size());
152 get_closest_mesh_tris(mesh, positions, mask, tri_indices, r_distances_sq, r_positions);
153
154 const Span<int> tri_faces = mesh.corner_tri_faces();
155
156 mask.foreach_index([&](const int i) { r_face_indices[i] = tri_faces[tri_indices[i]]; });
157}
158
159/* The closest corner is defined to be the closest corner on the closest face. */
160static void get_closest_mesh_corners(const Mesh &mesh,
161 const VArray<float3> &positions,
162 const IndexMask &mask,
163 const MutableSpan<int> r_corner_indices,
164 const MutableSpan<float> r_distances_sq,
165 const MutableSpan<float3> r_positions)
166{
167 const Span<float3> vert_positions = mesh.vert_positions();
168 const OffsetIndices faces = mesh.faces();
169 const Span<int> corner_verts = mesh.corner_verts();
170
171 BLI_assert(mesh.corners_num > 0);
172 Array<int> face_indices(positions.size());
173 get_closest_mesh_faces(mesh, positions, mask, face_indices, {}, {});
174
175 mask.foreach_index([&](const int i) {
176 const float3 position = positions[i];
177 const int face_index = face_indices[i];
178
179 /* Find the closest vertex in the face. */
180 float min_distance_sq = FLT_MAX;
181 int closest_vert = 0;
182 int closest_corner = 0;
183 for (const int corner : faces[face_index]) {
184 const int vert = corner_verts[corner];
185 const float distance_sq = math::distance_squared(position, vert_positions[vert]);
186 if (distance_sq < min_distance_sq) {
187 min_distance_sq = distance_sq;
188 closest_corner = corner;
189 closest_vert = vert;
190 }
191 }
192 if (!r_corner_indices.is_empty()) {
193 r_corner_indices[i] = closest_corner;
194 }
195 if (!r_positions.is_empty()) {
196 r_positions[i] = vert_positions[closest_vert];
197 }
198 if (!r_distances_sq.is_empty()) {
199 r_distances_sq[i] = min_distance_sq;
200 }
201 });
202}
203
205 const GeometryComponent::Type type,
206 const AttrDomain domain)
207{
208 if (!geometry.has(type)) {
209 return false;
210 }
211 const GeometryComponent &component = *geometry.get_component(type);
212 return component.attribute_domain_size(domain) != 0;
213}
214
216 const AttrDomain domain)
217{
218 /* Choose the other component based on a consistent order, rather than some more complicated
219 * heuristic. This is the same order visible in the spreadsheet and used in the ray-cast node. */
220 static const Array<GeometryComponent::Type> supported_types = {
221 GeometryComponent::Type::Mesh, GeometryComponent::Type::PointCloud};
222 for (const GeometryComponent::Type src_type : supported_types) {
223 if (component_is_available(geometry, src_type, domain)) {
224 return geometry.get_component(src_type);
225 }
226 }
227
228 return nullptr;
229}
230
231class SampleNearestFunction : public mf::MultiFunction {
232 GeometrySet source_;
233 AttrDomain domain_;
234
235 const GeometryComponent *src_component_;
236
237 /* Point clouds do not cache BVH trees currently; avoid rebuilding it on every call. */
238 bke::BVHTreeFromPointCloud pointcloud_bvh = {};
239
240 mf::Signature signature_;
241
242 public:
244 : source_(std::move(geometry)), domain_(domain)
245 {
246 source_.ensure_owns_direct_data();
247 this->src_component_ = find_source_component(source_, domain_);
248 if (src_component_ && src_component_->type() == bke::GeometryComponent::Type::PointCloud) {
249 const PointCloudComponent &component = *static_cast<const PointCloudComponent *>(
250 src_component_);
251 const PointCloud &points = *component.get();
252 pointcloud_bvh = bke::bvhtree_from_pointcloud_get(points, IndexMask(points.totpoint));
253 }
254
255 mf::SignatureBuilder builder{"Sample Nearest", signature_};
256 builder.single_input<float3>("Position");
257 builder.single_output<int>("Index");
258 this->set_signature(&signature_);
259 }
260
261 void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
262 {
263 const VArray<float3> &positions = params.readonly_single_input<float3>(0, "Position");
264 MutableSpan<int> indices = params.uninitialized_single_output<int>(1, "Index");
265 if (!src_component_) {
267 return;
268 }
269
270 switch (src_component_->type()) {
271 case GeometryComponent::Type::Mesh: {
272 const MeshComponent &component = *static_cast<const MeshComponent *>(src_component_);
273 const Mesh &mesh = *component.get();
274 switch (domain_) {
275 case AttrDomain::Point:
276 get_closest_mesh_points(mesh, positions, mask, indices, {}, {});
277 break;
278 case AttrDomain::Edge:
279 get_closest_mesh_edges(mesh, positions, mask, indices, {}, {});
280 break;
281 case AttrDomain::Face:
282 get_closest_mesh_faces(mesh, positions, mask, indices, {}, {});
283 break;
284 case AttrDomain::Corner:
285 get_closest_mesh_corners(mesh, positions, mask, indices, {}, {});
286 break;
287 default:
288 break;
289 }
290 break;
291 }
292 case GeometryComponent::Type::PointCloud: {
293 get_closest_pointcloud_points(pointcloud_bvh, positions, mask, indices, {});
294 break;
295 }
296 default:
297 break;
298 }
299 }
300};
301
303{
304 GeometrySet geometry = params.extract_input<GeometrySet>("Geometry");
305 const AttrDomain domain = AttrDomain(params.node().custom2);
306 if (geometry.has_curves() && !geometry.has_mesh() && !geometry.has_pointcloud()) {
307 params.error_message_add(NodeWarningType::Error,
308 TIP_("The source geometry must contain a mesh or a point cloud"));
309 params.set_default_remaining_outputs();
310 return;
311 }
312
313 Field<float3> positions = params.extract_input<Field<float3>>("Sample Position");
314 auto fn = std::make_shared<SampleNearestFunction>(std::move(geometry), domain);
315 auto op = FieldOperation::Create(std::move(fn), {std::move(positions)});
316 params.set_output<Field<int>>("Index", Field<int>(std::move(op)));
317}
318
319static void node_rna(StructRNA *srna)
320{
322 "domain",
323 "Domain",
324 "",
327 int(AttrDomain::Point));
328}
329
330static void node_register()
331{
332 static blender::bke::bNodeType ntype;
333
334 geo_node_type_base(&ntype, "GeometryNodeSampleNearest", GEO_NODE_SAMPLE_NEAREST);
335 ntype.ui_name = "Sample Nearest";
336 ntype.ui_description =
337 "Find the element of a geometry closest to a position. Similar to the \"Index of Nearest\" "
338 "node";
339 ntype.enum_name_legacy = "SAMPLE_NEAREST";
341 ntype.initfunc = node_init;
342 ntype.declare = node_declare;
346
347 node_rna(ntype.rna_ext.srna);
348}
350
351} // namespace blender::nodes::node_geo_sample_nearest_cc
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:447
#define GEO_NODE_SAMPLE_NEAREST
#define BLI_assert(a)
Definition BLI_assert.h:46
int BLI_bvhtree_find_nearest(const BVHTree *tree, const float co[3], BVHTreeNearest *nearest, BVHTree_NearestPointCallback callback, void *userdata)
#define TIP_(msgid)
@ CD_PROP_FLOAT
@ NODE_DEFAULT_INPUT_POSITION_FIELD
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_inline_enum_accessors(member)
#define UI_ITEM_NONE
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr bool is_empty() const
Definition BLI_span.hh:509
constexpr void fill(const T &value) const
Definition BLI_span.hh:517
int attribute_domain_size(AttrDomain domain) const
void set_signature(const Signature *signature)
static std::shared_ptr< FieldOperation > Create(std::shared_ptr< const mf::MultiFunction > function, Vector< GField > inputs={})
Definition FN_field.hh:242
void call(const IndexMask &mask, mf::Params params, mf::Context) const override
static ushort indices[]
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static char faces[256]
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
BVHTreeFromPointCloud bvhtree_from_pointcloud_get(const PointCloud &pointcloud, const IndexMask &points_mask)
Definition bvhutils.cc:842
void masked_fill(MutableSpan< T > data, const T &value, const IndexMask &mask)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
static void get_closest_mesh_faces(const Mesh &mesh, const VArray< float3 > &positions, const IndexMask &mask, const MutableSpan< int > r_face_indices, const MutableSpan< float > r_distances_sq, const MutableSpan< float3 > r_positions)
static void get_closest_mesh_edges(const Mesh &mesh, const VArray< float3 > &positions, const IndexMask &mask, const MutableSpan< int > r_edge_indices, const MutableSpan< float > r_distances_sq, const MutableSpan< float3 > r_positions)
static void get_closest_mesh_points(const Mesh &mesh, const VArray< float3 > &positions, const IndexMask &mask, const MutableSpan< int > r_point_indices, const MutableSpan< float > r_distances_sq, const MutableSpan< float3 > r_positions)
static void get_closest_pointcloud_points(const bke::BVHTreeFromPointCloud &tree_data, const VArray< float3 > &positions, const IndexMask &mask, MutableSpan< int > r_indices, MutableSpan< float > r_distances_sq)
static void get_closest_mesh_corners(const Mesh &mesh, const VArray< float3 > &positions, const IndexMask &mask, const MutableSpan< int > r_corner_indices, const MutableSpan< float > r_distances_sq, const MutableSpan< float3 > r_positions)
static void node_init(bNodeTree *, bNode *node)
static void node_declare(NodeDeclarationBuilder &b)
static void node_geo_exec(GeoNodeExecParams params)
static bool component_is_available(const GeometrySet &geometry, const GeometryComponent::Type type, const AttrDomain domain)
static const GeometryComponent * find_source_component(const GeometrySet &geometry, const AttrDomain domain)
static void get_closest_mesh_tris(const Mesh &mesh, const VArray< float3 > &positions, const IndexMask &mask, const MutableSpan< int > r_tri_indices, const MutableSpan< float > r_distances_sq, const MutableSpan< float3 > r_positions)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
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 get_closest_in_bvhtree(bke::BVHTreeFromMesh &tree_data, const VArray< float3 > &positions, const IndexMask &mask, MutableSpan< int > r_indices, MutableSpan< float > r_distances_sq, MutableSpan< float3 > r_positions)
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_only_mesh_items[]
#define FLT_MAX
Definition stdcycles.h:14
StructRNA * srna
Definition RNA_types.hh:909
int corners_num
int edges_num
int faces_num
int verts_num
int16_t custom1
int16_t custom2
BVHTree_NearestPointCallback nearest_callback
BVHTree_NearestPointCallback nearest_callback
Defines a node type.
Definition BKE_node.hh:226
std::string ui_description
Definition BKE_node.hh:232
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:277
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:347
const char * enum_name_legacy
Definition BKE_node.hh:235
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:355
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)
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4227