Geometry Node: Index of Nearest #104619
|
@ -220,6 +220,7 @@ class NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE(Menu):
|
|||
def draw(self, _context):
|
||||
layout = self.layout
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeProximity")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeIndexOfNearest")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeRaycast")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeSampleIndex")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeSampleNearest")
|
||||
|
|
|
@ -1581,6 +1581,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
|
|||
#define GEO_NODE_SDF_VOLUME_SPHERE 1196
|
||||
#define GEO_NODE_MEAN_FILTER_SDF_VOLUME 1197
|
||||
#define GEO_NODE_OFFSET_SDF_VOLUME 1198
|
||||
#define GEO_NODE_INDEX_OF_NEAREST 1199
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -105,6 +105,23 @@ inline void BLI_kdtree_nd_(range_search_cb_cpp)(const KDTree *tree,
|
|||
},
|
||||
const_cast<Fn *>(&fn));
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
inline int BLI_kdtree_nd_(find_nearest_cb_cpp)(const KDTree *tree,
|
||||
mod_moder marked this conversation as resolved
Outdated
|
||||
const float co[KD_DIMS],
|
||||
KDTreeNearest *r_nearest,
|
||||
Fn &&fn)
|
||||
Jacques Lucke
commented
Can just use Can just use `Fn &&fn`, then you also don't need the const cast below I think.
Iliya Katushenock
commented
Should i also change one other _cpp wrapper above? Should i also change one other _cpp wrapper above?
Jacques Lucke
commented
Just keep other code as is right now. Just keep other code as is right now.
|
||||
{
|
||||
return BLI_kdtree_nd_(find_nearest_cb)(
|
||||
tree,
|
||||
co,
|
||||
[](void *user_data, const int index, const float *co, const float dist_sq) {
|
||||
Fn &fn = *static_cast<Fn *>(user_data);
|
||||
return fn(index, co, dist_sq);
|
||||
},
|
||||
&fn,
|
||||
r_nearest);
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef _BLI_CONCAT_AUX
|
||||
|
|
|
@ -324,6 +324,7 @@ DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Fac
|
|||
DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large")
|
||||
DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image")
|
||||
DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture")
|
||||
DefNode(GeometryNode, GEO_NODE_INDEX_OF_NEAREST, 0, "INDEX_OF_NEAREST", IndexOfNearest, "Index of Nearest", "Find the nearest element in the a group. Similar to the \"Sample Nearest\" node")
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
For the description, I'd suggest: Adding For the description, I'd suggest:
`Find the nearest element in the a group`
Adding `. Similar to the \"Sample Nearest\" node` might be helpful too.
|
||||
DefNode(GeometryNode, GEO_NODE_IMAGE, def_geo_image, "IMAGE", InputImage, "Image", "Input image")
|
||||
DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_HANDLES, 0, "INPUT_CURVE_HANDLES", InputCurveHandlePositions,"Curve Handle Positions", "Retrieve the position of each Bézier control point's handles")
|
||||
DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_TILT, 0, "INPUT_CURVE_TILT", InputCurveTilt, "Curve Tilt", "Retrieve the angle at each control point used to twist the curve's normal around its tangent")
|
||||
|
@ -398,7 +399,7 @@ DefNode(GeometryNode, GEO_NODE_ROTATE_INSTANCES, 0, "ROTATE_INSTANCES", RotateIn
|
|||
DefNode(GeometryNode, GEO_NODE_SAMPLE_CURVE, def_geo_curve_sample, "SAMPLE_CURVE", SampleCurve, "Sample Curve", "Retrieve data from a point on a curve at a certain distance from its start")
|
||||
DefNode(GeometryNode, GEO_NODE_SAMPLE_INDEX, def_geo_sample_index, "SAMPLE_INDEX", SampleIndex, "Sample Index", "Retrieve values from specific geometry elements")
|
||||
DefNode(GeometryNode, GEO_NODE_SAMPLE_NEAREST_SURFACE, def_geo_sample_nearest_surface, "SAMPLE_NEAREST_SURFACE", SampleNearestSurface, "Sample Nearest Surface", "Calculate the interpolated value of a mesh attribute on the closest point of its surface")
|
||||
DefNode(GeometryNode, GEO_NODE_SAMPLE_NEAREST, def_geo_sample_nearest, "SAMPLE_NEAREST", SampleNearest, "Sample Nearest", "Find the element of a geometry closest to a position")
|
||||
DefNode(GeometryNode, GEO_NODE_SAMPLE_NEAREST, def_geo_sample_nearest, "SAMPLE_NEAREST", SampleNearest, "Sample Nearest", "Find the element of a geometry closest to a position. Similar to the \"Index of Nearest\" node")
|
||||
DefNode(GeometryNode, GEO_NODE_SAMPLE_UV_SURFACE, def_geo_sample_uv_surface, "SAMPLE_UV_SURFACE", SampleUVSurface, "Sample UV Surface", "Calculate the interpolated values of a mesh attribute at a UV coordinate")
|
||||
DefNode(GeometryNode, GEO_NODE_SCALE_ELEMENTS, def_geo_scale_elements, "SCALE_ELEMENTS", ScaleElements, "Scale Elements", "Scale groups of connected edges and faces")
|
||||
DefNode(GeometryNode, GEO_NODE_SCALE_INSTANCES, 0, "SCALE_INSTANCES", ScaleInstances, "Scale Instances", "Scale geometry instances in local or global space")
|
||||
|
|
|
@ -78,6 +78,7 @@ set(SRC
|
|||
nodes/node_geo_image.cc
|
||||
nodes/node_geo_image_info.cc
|
||||
nodes/node_geo_image_texture.cc
|
||||
nodes/node_geo_index_of_nearest.cc
|
||||
nodes/node_geo_input_curve_handles.cc
|
||||
nodes/node_geo_input_curve_tilt.cc
|
||||
nodes/node_geo_input_id.cc
|
||||
|
|
|
@ -62,6 +62,7 @@ void register_geometry_nodes()
|
|||
register_node_type_geo_image_info();
|
||||
register_node_type_geo_image_texture();
|
||||
register_node_type_geo_image();
|
||||
register_node_type_geo_index_of_nearest();
|
||||
register_node_type_geo_input_curve_handles();
|
||||
register_node_type_geo_input_curve_tilt();
|
||||
register_node_type_geo_input_id();
|
||||
|
|
|
@ -59,6 +59,7 @@ void register_node_type_geo_geometry_to_instance();
|
|||
void register_node_type_geo_image_info();
|
||||
void register_node_type_geo_image_texture();
|
||||
void register_node_type_geo_image();
|
||||
void register_node_type_geo_index_of_nearest();
|
||||
void register_node_type_geo_input_curve_handles();
|
||||
void register_node_type_geo_input_curve_tilt();
|
||||
void register_node_type_geo_input_id();
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_kdtree.h"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes::node_geo_index_of_nearest_cc {
|
||||
|
||||
mod_moder marked this conversation as resolved
Hans Goudey
commented
`BKE_geometry_fields.hh` is already included indirectly by `node_geometry_util.hh`
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Vector>(N_("Position")).implicit_field(implicit_field_inputs::position);
|
||||
b.add_input<decl::Int>(N_("Group ID")).supports_field().hide_value();
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
I think these UI includes are unused I think these UI includes are unused
|
||||
|
||||
b.add_output<decl::Int>(N_("Index")).field_source().description(N_("Index of nearest element"));
|
||||
b.add_output<decl::Bool>(N_("Has Neighbor")).field_source();
|
||||
}
|
||||
|
||||
static KDTree_3d *build_kdtree(const Span<float3> positions, const IndexMask mask)
|
||||
{
|
||||
KDTree_3d *tree = BLI_kdtree_3d_new(mask.size());
|
||||
for (const int index : mask) {
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
These identifiers/names are missing the translation macro These identifiers/names are missing the translation macro `N_`
|
||||
BLI_kdtree_3d_insert(tree, index, positions[index]);
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
We settled on We settled on `Self Group ID` and `Nearest Group ID` in the module meeting.
|
||||
}
|
||||
BLI_kdtree_3d_balance(tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
static int find_nearest_non_self(const KDTree_3d &tree, const float3 &position, const int index)
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
Pass spans by value Pass spans by value
|
||||
{
|
||||
return BLI_kdtree_3d_find_nearest_cb_cpp(
|
||||
&tree, position, 0, [index](const int other, const float * /*co*/, const float /*dist_sq*/) {
|
||||
return index == other ? 0 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
static void find_neighbors(const KDTree_3d &tree,
|
||||
const Span<float3> positions,
|
||||
const IndexMask mask,
|
||||
MutableSpan<int> r_indices)
|
||||
{
|
||||
threading::parallel_for(mask.index_range(), 512, [&](const IndexRange range) {
|
||||
for (const int index : mask.slice(range)) {
|
||||
r_indices[index] = find_nearest_non_self(tree, positions[index], index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class IndexOfNearestFieldInput final : public bke::GeometryFieldInput {
|
||||
Hans Goudey
commented
I don't think there's a benefit to specifying the capture in a case like this (instead of I don't think there's a benefit to specifying the capture in a case like this (instead of `[&]`)
|
||||
private:
|
||||
const Field<float3> positions_field_;
|
||||
const Field<int> group_field_;
|
||||
|
||||
public:
|
||||
IndexOfNearestFieldInput(Field<float3> positions_field, Field<int> group_field)
|
||||
: bke::GeometryFieldInput(CPPType::get<int>(), "Index of Nearest"),
|
||||
positions_field_(std::move(positions_field)),
|
||||
group_field_(std::move(group_field))
|
||||
{
|
||||
}
|
||||
|
||||
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
|
||||
const IndexMask mask) const final
|
||||
{
|
||||
Jacques Lucke
commented
Can just use a normal for loop here. The performance benefit of using Can just use a normal for loop here. The performance benefit of using `foreach_index` is negligible, because most time is spend in the kdtree traversal.
|
||||
if (!context.attributes()) {
|
||||
return {};
|
||||
}
|
||||
const int domain_size = context.attributes()->domain_size(context.domain());
|
||||
fn::FieldEvaluator evaluator{context, domain_size};
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
I'd change these names to I'd change these names to `use_group` and `use_search_group` for consistency with your other variable names and because `use` at front sounds more natural.
|
||||
evaluator.add(positions_field_);
|
||||
evaluator.add(group_field_);
|
||||
evaluator.evaluate();
|
||||
const VArraySpan<float3> positions = evaluator.get_evaluated<float3>(0);
|
||||
const VArray<int> group = evaluator.get_evaluated<int>(1);
|
||||
|
||||
Array<int> result;
|
||||
Hans Goudey
commented
This comment is a bit vague, but I'm finding it hard to keep track of the separate This comment is a bit vague, but I'm finding it hard to keep track of the separate `group_use` and `group_to_find_use` conditions. I wonder if the first condition could be handled with by splitting logic into a separate function, like `SampleCurveFunction` does with its `sample_curve` lambda.
|
||||
|
||||
if (group.is_single()) {
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
No strong opinion, but using regular No strong opinion, but using regular `=` assignment seems more consistent here.
|
||||
result.reinitialize(mask.min_array_size());
|
||||
KDTree_3d *tree = build_kdtree(positions, IndexRange(domain_size));
|
||||
find_neighbors(*tree, positions, mask, result);
|
||||
BLI_kdtree_3d_free(tree);
|
||||
return VArray<int>::ForContainer(std::move(result));
|
||||
}
|
||||
|
||||
VectorSet<int> group_indexing;
|
||||
for (const int index : mask) {
|
||||
Hans Goudey
commented
It seems like creating a KD tree for every group ID will mean that the KD tree for the same set of search points will potentially be built multiple times. There should probably be some way to avoid building a tree multiple times for the same key. Overall this area looks much cleaner than the last time I checked though! It seems like creating a KD tree for every group ID will mean that the KD tree for the same set of search points will potentially be built multiple times. There should probably be some way to avoid building a tree multiple times for the same key.
Overall this area looks much cleaner than the last time I checked though!
Iliya Katushenock
commented
After you point it out... it seems I made some kind of logical error in the implementation of switching groups when iterating over them. Or not .. i need to redo this part of the code After you point it out... it seems I made some kind of logical error in the implementation of switching groups when iterating over them. Or not .. i need to redo this part of the code
Hans Goudey
commented
Remove empty line between Remove empty line between `group_indexing` declaration and loop that creates it
|
||||
const int group_id = group[index];
|
||||
group_indexing.add(group_id);
|
||||
}
|
||||
|
||||
/* Each group id has two corresponding index masks. One that contains all the points in the
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
We can't selectively ignore the mask actually, it may be incorrect to write the output to non-selected indices. So this check really has to be We can't selectively ignore the mask actually, it may be incorrect to write the output to non-selected indices. So this check really has to be `mask.size() == domain_size`
Iliya Katushenock
commented
I see. I see.
Just if domain size is 10000, mask is 9999, is no much sense to compute the cheap mask. I think, is better to just allocate all elements if cheap mask is used.
|
||||
* group, one that contains all the points in the group that should be looked up (this is the
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
Replace this with Replace this with `const bool mask_is_full = mask.size() == domain_size;`
|
||||
* intersection of the points in the group and `mask`). In many cases, both of these masks are
|
||||
* the same or very similar, so there is no benefit two separate masks. */
|
||||
const bool use_separate_lookup_indices = mask.size() < domain_size / 2;
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
`int key` -> `const int key`
|
||||
|
||||
Array<Vector<int64_t>> all_indices_by_group_id(group_indexing.size());
|
||||
Array<Vector<int64_t>> lookup_indices_by_group_id;
|
||||
|
||||
if (use_separate_lookup_indices) {
|
||||
result.reinitialize(mask.min_array_size());
|
||||
lookup_indices_by_group_id.reinitialize(group_indexing.size());
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
The result array should never need to be larger than The result array should never need to be larger than `mask.min_array_size()`
|
||||
}
|
||||
else {
|
||||
result.reinitialize(domain_size);
|
||||
}
|
||||
|
||||
const auto build_group_masks = [&](const IndexMask mask,
|
||||
mod_moder marked this conversation as resolved
Outdated
Jacques Lucke
commented
While forest is used as a technical term for a graph containing multiple trees, I don't think the term should be used for a collection of multiple independent kd trees. Just use While forest is used as a technical term for a graph containing multiple trees, I don't think the term should be used for a collection of multiple independent kd trees. Just use `kdtrees`.
Iliya Katushenock
commented
If i do merge If i do merge `parallel_for`s below, i can delete this vector.
|
||||
MutableSpan<Vector<int64_t>> r_groups) {
|
||||
for (const int index : mask) {
|
||||
const int group_id = group[index];
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
I think this should be I think this should be `!mask_is_cheap`
Iliya Katushenock
commented
I was isn't invented the best name, I was isn't invented the best name, `mask_is_cheap` == true if computing of indices make sense and cheap mask is used.
|
||||
const int index_of_group = group_indexing.index_of_try(group_id);
|
||||
Hans Goudey
commented
TBH I think it's better to just use TBH I think it's better to just use `int64_t` here to avoid duplicating the functions above. Eventually when `IndexMask` is refactored, these could benefit from using that, and the `int64_t` will make that more clear too.
|
||||
if (index_of_group != -1) {
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
`Vector` -> `Array` here
|
||||
r_groups[index_of_group].append(index);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Hans Goudey
commented
`domain_size > 1024 && use_cheap_mask` -> `domain_size > 1024 && !mask_is_full`
|
||||
threading::parallel_invoke(
|
||||
Iliya Katushenock
commented
Are Are `grain_size` is a power of 2?
Hans Goudey
commented
They don't need to be powers of two, though I think it can be slightly beneficial. Since these numbers are a bit arbitrary anyway I started using powers of two. They don't need to be powers of two, though I think it can be slightly beneficial. Since these numbers are a bit arbitrary anyway I started using powers of two.
Iliya Katushenock
commented
I remember fixing a bug, only related to the fact that there was no power of 2x. I remember fixing a bug, only related to the fact that there was no power of 2x.
For the blur node, I made the search function for the top bit part in the threading namespace.
|
||||
domain_size > 1024 && use_separate_lookup_indices,
|
||||
[&]() {
|
||||
if (use_separate_lookup_indices) {
|
||||
Iliya Katushenock
commented
Why It seems that trying to use a mask to multiply all user groups by groups from the mask leads to much more overhead in most cases. Why `tree_masks[i]` and `evaluate_masks[i]` is groups with the same id? this isn't sorted somether or protected, that mask isn't avoid all elements of some grouop and create offset for all other groups even all of that added in same order.
It seems that trying to use a mask to multiply all user groups by groups from the mask leads to much more overhead in most cases.
If you don't do some kind of analogue of a tree, then it's like doing a boolean through mask1 * mask2. When there is something like bvh. so I think it's easier to just calculate everything. I'm not sure there are many cases now where the mask can actually be a small part.
Hans Goudey
commented
Oh, great point! I think it's still worth having a separate evaluation mask. Even just the set position node with a small selection would benefit from it, I think kd tree lookups are fairly expensive. I guess both masks have to be created at the same time. Oh, great point! I think it's still worth having a separate evaluation mask. Even just the set position node with a small selection would benefit from it, I think kd tree lookups are fairly expensive. I guess both masks have to be created at the same time.
|
||||
build_group_masks(mask, lookup_indices_by_group_id);
|
||||
}
|
||||
},
|
||||
[&]() { build_group_masks(IndexMask(domain_size), all_indices_by_group_id); });
|
||||
|
||||
Hans Goudey
commented
I do think one of these indices could be skipped if the selection is complete I do think one of these indices could be skipped if the selection is complete
Hans Goudey
commented
Use Use `IndexMask` instead of `Span<int64_t>` for these `mask_of_tree` and `mask` variables
|
||||
threading::parallel_for(group_indexing.index_range(), 256, [&](const IndexRange range) {
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
References typically indicate a lack of ownership, better to stick with a pointer here References typically indicate a lack of ownership, better to stick with a pointer here
|
||||
for (const int index : range) {
|
||||
const IndexMask tree_mask = all_indices_by_group_id[index].as_span();
|
||||
Jacques Lucke
commented
Feels like this Feels like this `parallel_for` loop and the one below can be combined into one. This could potentially improve multi-threaded performance.
|
||||
const IndexMask lookup_mask = use_separate_lookup_indices ?
|
||||
IndexMask(lookup_indices_by_group_id[index]) :
|
||||
tree_mask;
|
||||
KDTree_3d *tree = build_kdtree(positions, tree_mask);
|
||||
find_neighbors(*tree, positions, lookup_mask, result);
|
||||
BLI_kdtree_3d_free(tree);
|
||||
}
|
||||
});
|
||||
|
||||
return VArray<int>::ForContainer(std::move(result));
|
||||
}
|
||||
|
||||
public:
|
||||
void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const
|
||||
{
|
||||
positions_field_.node().for_each_field_input_recursive(fn);
|
||||
group_field_.node().for_each_field_input_recursive(fn);
|
||||
}
|
||||
|
||||
uint64_t hash() const final
|
||||
{
|
||||
return get_default_hash_2(positions_field_, group_field_);
|
||||
}
|
||||
|
||||
bool is_equal_to(const fn::FieldNode &other) const final
|
||||
{
|
||||
if (const auto *other_field = dynamic_cast<const IndexOfNearestFieldInput *>(&other)) {
|
||||
return positions_field_ == other_field->positions_field_ &&
|
||||
group_field_ == other_field->group_field_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
If using I think it would be fine to retrieve them directly in If using `std::move` with these variables, don't declare them const.
I think it would be fine to retrieve them directly in `make_shared` though, rather than having temporary variables.
|
||||
|
||||
std::optional<eAttrDomain> preferred_domain(const GeometryComponent &component) const final
|
||||
{
|
||||
return bke::try_detect_field_domain(component, positions_field_);
|
||||
}
|
||||
};
|
||||
|
||||
class HasNeighborFieldInput final : public bke::GeometryFieldInput {
|
||||
private:
|
||||
const Field<int> group_field_;
|
||||
|
||||
public:
|
||||
HasNeighborFieldInput(Field<int> group_field)
|
||||
: bke::GeometryFieldInput(CPPType::get<bool>(), "Has Neighbor"),
|
||||
group_field_(std::move(group_field))
|
||||
{
|
||||
}
|
||||
|
||||
GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
|
||||
const IndexMask mask) const final
|
||||
{
|
||||
if (!context.attributes()) {
|
||||
return {};
|
||||
}
|
||||
const int domain_size = context.attributes()->domain_size(context.domain());
|
||||
if (domain_size == 1) {
|
||||
return VArray<bool>::ForSingle(false, mask.min_array_size());
|
||||
}
|
||||
|
||||
fn::FieldEvaluator evaluator{context, domain_size};
|
||||
evaluator.add(group_field_);
|
||||
evaluator.evaluate();
|
||||
const VArray<int> group = evaluator.get_evaluated<int>(0);
|
||||
|
||||
if (group.is_single()) {
|
||||
return VArray<bool>::ForSingle(true, mask.min_array_size());
|
||||
}
|
||||
|
||||
Map<int, int> counts;
|
||||
mod_moder marked this conversation as resolved
Outdated
Hans Goudey
commented
Oops, I forgot to remove this comment. Oops, I forgot to remove this comment.
|
||||
const VArraySpan<int> group_span(group);
|
||||
mask.foreach_index([&](const int i) {
|
||||
counts.add_or_modify(
|
||||
group_span[i], [](int *count) { *count = 0; }, [](int *count) { (*count)++; });
|
||||
});
|
||||
Array<bool> result(mask.min_array_size());
|
||||
mask.foreach_index([&](const int i) { result[i] = counts.lookup(group_span[i]) > 1; });
|
||||
return VArray<bool>::ForContainer(std::move(result));
|
||||
}
|
||||
|
||||
public:
|
||||
void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const
|
||||
{
|
||||
group_field_.node().for_each_field_input_recursive(fn);
|
||||
}
|
||||
|
||||
uint64_t hash() const final
|
||||
{
|
||||
return get_default_hash_2(3984756934876, group_field_);
|
||||
}
|
||||
|
||||
bool is_equal_to(const fn::FieldNode &other) const final
|
||||
{
|
||||
if (const auto *other_field = dynamic_cast<const HasNeighborFieldInput *>(&other)) {
|
||||
return group_field_ == other_field->group_field_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<eAttrDomain> preferred_domain(const GeometryComponent &component) const final
|
||||
{
|
||||
return bke::try_detect_field_domain(component, group_field_);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
Field<float3> position_field = params.extract_input<Field<float3>>("Position");
|
||||
Field<int> group_field = params.extract_input<Field<int>>("Group ID");
|
||||
|
||||
if (params.output_is_required("Index")) {
|
||||
params.set_output("Index",
|
||||
Field<int>(std::make_shared<IndexOfNearestFieldInput>(
|
||||
std::move(position_field), group_field)));
|
||||
}
|
||||
|
||||
if (params.output_is_required("Has Neighbor")) {
|
||||
params.set_output(
|
||||
"Has Neighbor",
|
||||
Field<bool>(std::make_shared<HasNeighborFieldInput>(std::move(group_field))));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::nodes::node_geo_index_of_nearest_cc
|
||||
|
||||
void register_node_type_geo_index_of_nearest()
|
||||
{
|
||||
namespace file_ns = blender::nodes::node_geo_index_of_nearest_cc;
|
||||
|
||||
static bNodeType ntype;
|
||||
|
||||
geo_node_type_base(&ntype, GEO_NODE_INDEX_OF_NEAREST, "Index of Nearest", NODE_CLASS_CONVERTER);
|
||||
ntype.geometry_node_execute = file_ns::node_geo_exec;
|
||||
ntype.declare = file_ns::node_declare;
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
Might as well be consistent with the other function here and use the
_cpp
suffix too.