From 84ac3d4edc76dae48097d2bcdbd561d75a65b52a Mon Sep 17 00:00:00 2001 From: illua1 Date: Sat, 11 Feb 2023 17:54:06 +0300 Subject: [PATCH 01/17] init commit of added index of nearest node --- .../startup/bl_ui/node_add_menu_geometry.py | 1 + source/blender/blenkernel/BKE_node.h | 1 + source/blender/blenlib/BLI_kdtree_impl.h | 17 ++ source/blender/nodes/NOD_static_types.h | 1 + source/blender/nodes/geometry/CMakeLists.txt | 1 + .../nodes/geometry/node_geometry_register.cc | 1 + .../nodes/geometry/node_geometry_register.hh | 1 + .../nodes/node_geo_index_of_nearest.cc | 185 ++++++++++++++++++ 8 files changed, 208 insertions(+) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc diff --git a/release/scripts/startup/bl_ui/node_add_menu_geometry.py b/release/scripts/startup/bl_ui/node_add_menu_geometry.py index 43fd87d0f83..f6011eaa19d 100644 --- a/release/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/release/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -520,6 +520,7 @@ class NODE_MT_category_GEO_UTILITIES_FIELD(Menu): node_add_menu.add_node_type(layout, "GeometryNodeAccumulateField") node_add_menu.add_node_type(layout, "GeometryNodeFieldAtIndex") node_add_menu.add_node_type(layout, "GeometryNodeFieldOnDomain") + node_add_menu.add_node_type(layout, "GeometryNodeIndexOfNearest") node_add_menu.draw_assets_for_catalog(layout, self.bl_label) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index f387122079b..47a37c64b10 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1541,6 +1541,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define GEO_NODE_IMAGE 1191 #define GEO_NODE_INTERPOLATE_CURVES 1192 #define GEO_NODE_EDGES_TO_FACE_GROUPS 1193 +#define GEO_NODE_INDEX_OF_NEAREST 1194 /** \} */ diff --git a/source/blender/blenlib/BLI_kdtree_impl.h b/source/blender/blenlib/BLI_kdtree_impl.h index 4187724fbda..0ea3e26ff2b 100644 --- a/source/blender/blenlib/BLI_kdtree_impl.h +++ b/source/blender/blenlib/BLI_kdtree_impl.h @@ -105,6 +105,23 @@ inline void BLI_kdtree_nd_(range_search_cb_cpp)(const KDTree *tree, }, const_cast(&fn)); } + +template +inline int BLI_kdtree_nd_(find_nearest_cb)(const KDTree *tree, + const float co[KD_DIMS], + KDTreeNearest *r_nearest, + const Fn &fn) +{ + return BLI_kdtree_nd_(find_nearest_cb)( + tree, + co, + [](void *user_data, const int index, const float *co, const float dist_sq) { + const Fn &fn = *static_cast(user_data); + return fn(index, co, dist_sq); + }, + const_cast(&fn), + r_nearest); +} #endif #undef _BLI_CONCAT_AUX diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index cb97e88ac9f..ff4c0bd2834 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -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", "Index of nearest element by groups condition") 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") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 448a5e69de4..8d6b7cd729f 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -77,6 +77,7 @@ set(SRC nodes/node_geo_geometry_to_instance.cc nodes/node_geo_image_info.cc nodes/node_geo_image_texture.cc + nodes/node_geo_index_of_nearest.cc nodes/node_geo_image.cc nodes/node_geo_input_curve_handles.cc nodes/node_geo_input_curve_tilt.cc diff --git a/source/blender/nodes/geometry/node_geometry_register.cc b/source/blender/nodes/geometry/node_geometry_register.cc index c6e0fc2697a..395292ff181 100644 --- a/source/blender/nodes/geometry/node_geometry_register.cc +++ b/source/blender/nodes/geometry/node_geometry_register.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(); diff --git a/source/blender/nodes/geometry/node_geometry_register.hh b/source/blender/nodes/geometry/node_geometry_register.hh index 900dd67cf71..267c8ebe751 100644 --- a/source/blender/nodes/geometry/node_geometry_register.hh +++ b/source/blender/nodes/geometry/node_geometry_register.hh @@ -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(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc new file mode 100644 index 00000000000..3c739b22446 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute_math.hh" + +#include "NOD_socket_search_link.hh" + +#include "BLI_kdtree.h" +#include "BLI_multi_value_map.hh" +#include "BLI_task.hh" + +#include "BKE_geometry_fields.hh" + +#include "node_geometry_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +namespace blender::nodes::node_geo_index_of_nearest_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Position").implicit_field(implicit_field_inputs::position); + + b.add_input("Self Group ID").supports_field().hide_value().default_value(0); + b.add_input("Group ID to Search").supports_field().hide_value().default_value(0); + + b.add_output("Index").field_source().description(N_("Index of nearest element")); +} + +int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, int index) +{ + return BLI_kdtree_3d_find_nearest_cb( + tree, position, 0, [index](const int other_new_i, const float * /*co*/, float /*dist_sq*/) { + if (index == other_new_i) { + return 0; + } + return 1; + }); +} + +class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { + private: + const Field positions_; + const Field group_; + const Field search_group_; + + public: + IndexOfNearestFieldInput(const Field positions, + const Field group, + const Field search_group) + : bke::GeometryFieldInput(CPPType::get(), "Nearest to"), + positions_(std::move(positions)), + group_(std::move(group)), + search_group_(std::move(search_group)) + { + } + + GVArray get_varray_for_context(const bke::GeometryFieldContext &context, + IndexMask mask) const final + { + fn::FieldEvaluator evaluator{context, &mask}; + evaluator.add(positions_); + evaluator.add(group_); + evaluator.add(search_group_); + evaluator.evaluate(); + + const VArray positions = evaluator.get_evaluated(0); + const VArray group = evaluator.get_evaluated(1); + const VArray search_group = evaluator.get_evaluated(2); + + const bool group_use = !group.is_single(); + const bool group_to_find_use = !search_group.is_single(); + + MultiValueMap in_group; + MultiValueMap out_group; + Array indices(mask.min_array_size()); + + threading::parallel_invoke((indices.size() > 512) && group_to_find_use && group_use, + [&]() { + if (group_use) { + for (const int64_t i : mask.index_range()) { + in_group.add(group[mask[i]], i); + } + } + else { + const int group_key = group.get_internal_single(); + in_group.add_multiple(group_key, {}); + } + }, + [&]() { + if (group_to_find_use) { + for (const int64_t i : mask.index_range()) { + out_group.add(search_group[mask[i]], i); + } + } + }); + + for (int key : in_group.keys()) { + /* Never empty. */ + const Span self_points = group_use ? in_group.lookup(key) : mask.indices(); + const Span search_points = group_to_find_use ? out_group.lookup(key) : self_points; + + if (search_points.is_empty()) { + indices.as_mutable_span().fill_indices(self_points, 0); + continue; + } + + KDTree_3d *tree = BLI_kdtree_3d_new(search_points.size()); + for (const int64_t index : search_points) { + BLI_kdtree_3d_insert(tree, index, positions[index]); + } + + BLI_kdtree_3d_balance(tree); + + threading::parallel_for(self_points.index_range(), 128, [&](const IndexRange range) { + for (const int64_t index : self_points.slice(range)) { + const int index_of_nearest = kdtree_find_neighboard(tree, positions[index], index); + if (index == -1) { + indices[index] = index; + } + else { + indices[index] = index_of_nearest; + } + } + }); + + BLI_kdtree_3d_free(tree); + } + + return VArray::ForContainer(std::move(indices)); + } + + void for_each_field_input_recursive(FunctionRef fn) const + { + positions_.node().for_each_field_input_recursive(fn); + group_.node().for_each_field_input_recursive(fn); + search_group_.node().for_each_field_input_recursive(fn); + } + + uint64_t hash() const override + { + return get_default_hash_3(positions_, group_, search_group_); + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (const IndexOfNearestFieldInput *other_field = + dynamic_cast(&other)) { + return positions_ == other_field->positions_ && group_ == other_field->group_ && + search_group_ == other_field->search_group_; + } + return false; + } + + std::optional preferred_domain(const GeometryComponent &component) const override + { + return bke::try_detect_field_domain(component, positions_); + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const Field position = params.extract_input>("Position"); + + const Field self_group = params.extract_input>("Self Group ID"); + const Field search_group = params.extract_input>("Group ID to Search"); + + params.set_output("Index", + Field{std::make_shared( + std::move(position), std::move(self_group), std::move(search_group))}); +} + +} // 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); +} -- 2.30.2 From 9b15dc86c27679ffb81dfa354f7d3e387bebb277 Mon Sep 17 00:00:00 2001 From: illua1 Date: Tue, 4 Apr 2023 17:38:20 +0300 Subject: [PATCH 02/17] Fix node name Fix node name and also old missing bug with indices for empty result --- source/blender/nodes/NOD_static_types.h | 2 +- .../blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 6385e14ad54..ade438c2f0d 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -324,7 +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", "Index of nearest element by groups condition") +DefNode(GeometryNode, GEO_NODE_INDEX_OF_NEAREST, 0, "INDEX_OF_NEAREST", IndexOfNearest, "Index of Nearest", "Index of nearest element by groups condition") 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") diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index 3c739b22446..c5cbad1a2d4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -115,7 +115,7 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { threading::parallel_for(self_points.index_range(), 128, [&](const IndexRange range) { for (const int64_t index : self_points.slice(range)) { const int index_of_nearest = kdtree_find_neighboard(tree, positions[index], index); - if (index == -1) { + if (index_of_nearest == -1) { indices[index] = index; } else { @@ -178,7 +178,7 @@ void register_node_type_geo_index_of_nearest() static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_INDEX_OF_NEAREST, "Index Of Nearest", NODE_CLASS_CONVERTER); + 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); -- 2.30.2 From a7f477b2efa59bdee417b4a895c26310428544d3 Mon Sep 17 00:00:00 2001 From: illua1 Date: Wed, 5 Apr 2023 19:39:38 +0300 Subject: [PATCH 03/17] changes for last comments --- source/blender/nodes/NOD_static_types.h | 2 +- .../nodes/node_geo_index_of_nearest.cc | 138 ++++++++++-------- 2 files changed, 77 insertions(+), 63 deletions(-) diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index ade438c2f0d..e64104111d1 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -324,7 +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", "Index of nearest element by groups condition") +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") 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") diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index c5cbad1a2d4..90573f58efe 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -8,34 +8,19 @@ #include "BLI_multi_value_map.hh" #include "BLI_task.hh" -#include "BKE_geometry_fields.hh" - #include "node_geometry_util.hh" -#include "UI_interface.h" -#include "UI_resources.h" - namespace blender::nodes::node_geo_index_of_nearest_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Position").implicit_field(implicit_field_inputs::position); + b.add_input(N_("Position")).implicit_field(implicit_field_inputs::position); - b.add_input("Self Group ID").supports_field().hide_value().default_value(0); - b.add_input("Group ID to Search").supports_field().hide_value().default_value(0); + b.add_input(N_("Self Group ID")).supports_field().hide_value().default_value(0); + b.add_input(N_("Nearest Group ID")).supports_field().hide_value().default_value(0); - b.add_output("Index").field_source().description(N_("Index of nearest element")); -} - -int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, int index) -{ - return BLI_kdtree_3d_find_nearest_cb( - tree, position, 0, [index](const int other_new_i, const float * /*co*/, float /*dist_sq*/) { - if (index == other_new_i) { - return 0; - } - return 1; - }); + b.add_output(N_("Index")).field_source().description(N_("Index of nearest element")); + b.add_output(N_("Valid")).field_source(); } class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { @@ -64,63 +49,56 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { evaluator.add(search_group_); evaluator.evaluate(); - const VArray positions = evaluator.get_evaluated(0); - const VArray group = evaluator.get_evaluated(1); - const VArray search_group = evaluator.get_evaluated(2); + const VArray &positions = evaluator.get_evaluated(0); + const VArray &group = evaluator.get_evaluated(1); + const VArray &search_group = evaluator.get_evaluated(2); - const bool group_use = !group.is_single(); - const bool group_to_find_use = !search_group.is_single(); + const bool use_group = !group.is_single(); + const bool use_search_group = !search_group.is_single(); MultiValueMap in_group; MultiValueMap out_group; Array indices(mask.min_array_size()); - threading::parallel_invoke((indices.size() > 512) && group_to_find_use && group_use, - [&]() { - if (group_use) { - for (const int64_t i : mask.index_range()) { - in_group.add(group[mask[i]], i); - } - } - else { - const int group_key = group.get_internal_single(); - in_group.add_multiple(group_key, {}); - } - }, - [&]() { - if (group_to_find_use) { - for (const int64_t i : mask.index_range()) { - out_group.add(search_group[mask[i]], i); - } - } - }); + threading::parallel_invoke( + (indices.size() > 512) && use_search_group && use_group, + [&]() { + if (use_group) { + mask.foreach_index([&](const auto index) { in_group.add(group[index], index); }); + return; + } + const int group_key = group.get_internal_single(); + in_group.add_multiple(group_key, {}); + }, + [&]() { + if (use_search_group) { + mask.foreach_index( + [&](const auto index) { out_group.add(search_group[index], index); }); + } + }); - for (int key : in_group.keys()) { + for (const int key : in_group.keys()) { /* Never empty. */ - const Span self_points = group_use ? in_group.lookup(key) : mask.indices(); - const Span search_points = group_to_find_use ? out_group.lookup(key) : self_points; + const IndexMask self_points(use_group ? IndexMask(in_group.lookup(key)) : mask); + const IndexMask search_points(use_search_group ? IndexMask(out_group.lookup(key)) : + self_points); if (search_points.is_empty()) { - indices.as_mutable_span().fill_indices(self_points, 0); + indices.as_mutable_span().fill_indices(self_points, -1); continue; } KDTree_3d *tree = BLI_kdtree_3d_new(search_points.size()); + for (const int64_t index : search_points) { BLI_kdtree_3d_insert(tree, index, positions[index]); } BLI_kdtree_3d_balance(tree); - threading::parallel_for(self_points.index_range(), 128, [&](const IndexRange range) { + threading::parallel_for(self_points.index_range(), 512, [&](const IndexRange range) { for (const int64_t index : self_points.slice(range)) { - const int index_of_nearest = kdtree_find_neighboard(tree, positions[index], index); - if (index_of_nearest == -1) { - indices[index] = index; - } - else { - indices[index] = index_of_nearest; - } + indices[index] = this->kdtree_find_neighboard(tree, positions[index], index); } }); @@ -130,6 +108,22 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { return VArray::ForContainer(std::move(indices)); } + protected: + int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, int index) const + { + return BLI_kdtree_3d_find_nearest_cb( + tree, + position, + 0, + [index](const int other_new_i, const float * /*co*/, float /*dist_sq*/) { + if (index == other_new_i) { + return 0; + } + return 1; + }); + } + + public: void for_each_field_input_recursive(FunctionRef fn) const { positions_.node().for_each_field_input_recursive(fn); @@ -160,14 +154,34 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { static void node_geo_exec(GeoNodeExecParams params) { - const Field position = params.extract_input>("Position"); + Field position_field = params.extract_input>("Position"); - const Field self_group = params.extract_input>("Self Group ID"); - const Field search_group = params.extract_input>("Group ID to Search"); + Field self_group_field = params.extract_input>("Self Group ID"); + Field search_group_field = params.extract_input>("Nearest Group ID"); - params.set_output("Index", - Field{std::make_shared( - std::move(position), std::move(self_group), std::move(search_group))}); + Field index_of_nearest_field(std::make_shared( + std::move(position_field), std::move(self_group_field), std::move(search_group_field))); + + if (params.output_is_required("Index")) { + static auto clamp_fn = mf::build::SI1_SO( + "Clamp", + [](const int index) { return math::max(0, index); }, + mf::build::exec_presets::AllSpanOrSingle()); + auto clamp_op = std::make_shared( + FieldOperation(std::move(clamp_fn), {index_of_nearest_field})); + params.set_output("Index", Field(clamp_op, 0)); + } + + if (params.output_is_required("Valid")) { + static auto valid_fn = mf::build::SI1_SO( + "Valid Index", + [](const int index) { return index != -1; }, + mf::build::exec_presets::AllSpanOrSingle()); + + auto valid_op = std::make_shared( + FieldOperation(std::move(valid_fn), {std::move(index_of_nearest_field)})); + params.set_output("Valid", Field(valid_op, 0)); + } } } // namespace blender::nodes::node_geo_index_of_nearest_cc -- 2.30.2 From a5272482aa57b6f4f113862a706a781efa51668a Mon Sep 17 00:00:00 2001 From: illua1 Date: Wed, 5 Apr 2023 20:04:47 +0300 Subject: [PATCH 04/17] _cpp --- source/blender/blenlib/BLI_kdtree_impl.h | 8 ++++---- .../nodes/geometry/nodes/node_geo_index_of_nearest.cc | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/blender/blenlib/BLI_kdtree_impl.h b/source/blender/blenlib/BLI_kdtree_impl.h index 0ea3e26ff2b..4e01048c5d5 100644 --- a/source/blender/blenlib/BLI_kdtree_impl.h +++ b/source/blender/blenlib/BLI_kdtree_impl.h @@ -107,10 +107,10 @@ inline void BLI_kdtree_nd_(range_search_cb_cpp)(const KDTree *tree, } template -inline int BLI_kdtree_nd_(find_nearest_cb)(const KDTree *tree, - const float co[KD_DIMS], - KDTreeNearest *r_nearest, - const Fn &fn) +inline int BLI_kdtree_nd_(find_nearest_cb_cpp)(const KDTree *tree, + const float co[KD_DIMS], + KDTreeNearest *r_nearest, + const Fn &fn) { return BLI_kdtree_nd_(find_nearest_cb)( tree, diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index 90573f58efe..8da69ebaebf 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -111,7 +111,7 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { protected: int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, int index) const { - return BLI_kdtree_3d_find_nearest_cb( + return BLI_kdtree_3d_find_nearest_cb_cpp( tree, position, 0, -- 2.30.2 From 8fbcab83687fbc37b48671a9eda7952bc85377a7 Mon Sep 17 00:00:00 2001 From: illua1 Date: Thu, 6 Apr 2023 00:06:24 +0300 Subject: [PATCH 05/17] minor changes --- .../nodes/node_geo_index_of_nearest.cc | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index 8da69ebaebf..3376738fc0e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -2,8 +2,6 @@ #include "BKE_attribute_math.hh" -#include "NOD_socket_search_link.hh" - #include "BLI_kdtree.h" #include "BLI_multi_value_map.hh" #include "BLI_task.hh" @@ -25,18 +23,18 @@ static void node_declare(NodeDeclarationBuilder &b) class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { private: - const Field positions_; - const Field group_; - const Field search_group_; + const Field positions_field_; + const Field group_field_; + const Field search_group_field_; public: - IndexOfNearestFieldInput(const Field positions, - const Field group, - const Field search_group) + IndexOfNearestFieldInput(const Field positions_field, + const Field group_field, + const Field search_group_field) : bke::GeometryFieldInput(CPPType::get(), "Nearest to"), - positions_(std::move(positions)), - group_(std::move(group)), - search_group_(std::move(search_group)) + positions_field_(std::move(positions_field)), + group_field_(std::move(group_field)), + search_group_field_(std::move(search_group_field)) { } @@ -44,9 +42,9 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { IndexMask mask) const final { fn::FieldEvaluator evaluator{context, &mask}; - evaluator.add(positions_); - evaluator.add(group_); - evaluator.add(search_group_); + evaluator.add(positions_field_); + evaluator.add(group_field_); + evaluator.add(search_group_field_); evaluator.evaluate(); const VArray &positions = evaluator.get_evaluated(0); @@ -109,13 +107,13 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { } protected: - int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, int index) const + int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, const int index) const { return BLI_kdtree_3d_find_nearest_cb_cpp( tree, position, 0, - [index](const int other_new_i, const float * /*co*/, float /*dist_sq*/) { + [index](const int other_new_i, const float * /*co*/, const float /*dist_sq*/) { if (index == other_new_i) { return 0; } @@ -126,29 +124,30 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { public: void for_each_field_input_recursive(FunctionRef fn) const { - positions_.node().for_each_field_input_recursive(fn); - group_.node().for_each_field_input_recursive(fn); - search_group_.node().for_each_field_input_recursive(fn); + positions_field_.node().for_each_field_input_recursive(fn); + group_field_.node().for_each_field_input_recursive(fn); + search_group_field_.node().for_each_field_input_recursive(fn); } uint64_t hash() const override { - return get_default_hash_3(positions_, group_, search_group_); + return get_default_hash_3(positions_field_, group_field_, search_group_field_); } bool is_equal_to(const fn::FieldNode &other) const override { if (const IndexOfNearestFieldInput *other_field = dynamic_cast(&other)) { - return positions_ == other_field->positions_ && group_ == other_field->group_ && - search_group_ == other_field->search_group_; + return positions_field_ == other_field->positions_field_ && + group_field_ == other_field->group_field_ && + search_group_field_ == other_field->search_group_field_; } return false; } std::optional preferred_domain(const GeometryComponent &component) const override { - return bke::try_detect_field_domain(component, positions_); + return bke::try_detect_field_domain(component, positions_field_); } }; @@ -164,9 +163,9 @@ static void node_geo_exec(GeoNodeExecParams params) if (params.output_is_required("Index")) { static auto clamp_fn = mf::build::SI1_SO( - "Clamp", + "Index Clamping", [](const int index) { return math::max(0, index); }, - mf::build::exec_presets::AllSpanOrSingle()); + mf::build::exec_presets::Materialized()); auto clamp_op = std::make_shared( FieldOperation(std::move(clamp_fn), {index_of_nearest_field})); params.set_output("Index", Field(clamp_op, 0)); @@ -174,10 +173,9 @@ static void node_geo_exec(GeoNodeExecParams params) if (params.output_is_required("Valid")) { static auto valid_fn = mf::build::SI1_SO( - "Valid Index", + "Index Validating", [](const int index) { return index != -1; }, - mf::build::exec_presets::AllSpanOrSingle()); - + mf::build::exec_presets::Materialized()); auto valid_op = std::make_shared( FieldOperation(std::move(valid_fn), {std::move(index_of_nearest_field)})); params.set_output("Valid", Field(valid_op, 0)); -- 2.30.2 From f1720d20227cc99deb431722f7b03ba67d938130 Mon Sep 17 00:00:00 2001 From: illua1 Date: Tue, 18 Apr 2023 22:34:33 +0300 Subject: [PATCH 06/17] redurand const, change Valid output name --- .../geometry/nodes/node_geo_index_of_nearest.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index 3376738fc0e..4fbda3646b1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -18,7 +18,7 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input(N_("Nearest Group ID")).supports_field().hide_value().default_value(0); b.add_output(N_("Index")).field_source().description(N_("Index of nearest element")); - b.add_output(N_("Valid")).field_source(); + b.add_output(N_("Has Neighbor")).field_source(); } class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { @@ -28,9 +28,9 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { const Field search_group_field_; public: - IndexOfNearestFieldInput(const Field positions_field, - const Field group_field, - const Field search_group_field) + IndexOfNearestFieldInput(Field positions_field, + Field group_field, + Field search_group_field) : bke::GeometryFieldInput(CPPType::get(), "Nearest to"), positions_field_(std::move(positions_field)), group_field_(std::move(group_field)), @@ -107,7 +107,7 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { } protected: - int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, const int index) const + int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, const int &index) const { return BLI_kdtree_3d_find_nearest_cb_cpp( tree, @@ -171,14 +171,14 @@ static void node_geo_exec(GeoNodeExecParams params) params.set_output("Index", Field(clamp_op, 0)); } - if (params.output_is_required("Valid")) { + if (params.output_is_required("Has Neighbor")) { static auto valid_fn = mf::build::SI1_SO( "Index Validating", [](const int index) { return index != -1; }, mf::build::exec_presets::Materialized()); auto valid_op = std::make_shared( FieldOperation(std::move(valid_fn), {std::move(index_of_nearest_field)})); - params.set_output("Valid", Field(valid_op, 0)); + params.set_output("Has Neighbor", Field(valid_op, 0)); } } -- 2.30.2 From 3dc61ed8097a8d4fc9340ff8e7439044005b6666 Mon Sep 17 00:00:00 2001 From: illua1 Date: Wed, 19 Apr 2023 18:15:08 +0300 Subject: [PATCH 07/17] take off extra option --- .../nodes/node_geo_index_of_nearest.cc | 89 ++++++------------- 1 file changed, 28 insertions(+), 61 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index 4fbda3646b1..820f5219685 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -13,9 +13,7 @@ namespace blender::nodes::node_geo_index_of_nearest_cc { static void node_declare(NodeDeclarationBuilder &b) { b.add_input(N_("Position")).implicit_field(implicit_field_inputs::position); - - b.add_input(N_("Self Group ID")).supports_field().hide_value().default_value(0); - b.add_input(N_("Nearest Group ID")).supports_field().hide_value().default_value(0); + b.add_input(N_("Group ID")).supports_field().hide_value(); b.add_output(N_("Index")).field_source().description(N_("Index of nearest element")); b.add_output(N_("Has Neighbor")).field_source(); @@ -25,16 +23,12 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { private: const Field positions_field_; const Field group_field_; - const Field search_group_field_; public: - IndexOfNearestFieldInput(Field positions_field, - Field group_field, - Field search_group_field) + IndexOfNearestFieldInput(Field positions_field, Field group_field) : bke::GeometryFieldInput(CPPType::get(), "Nearest to"), positions_field_(std::move(positions_field)), - group_field_(std::move(group_field)), - search_group_field_(std::move(search_group_field)) + group_field_(std::move(group_field)) { } @@ -44,70 +38,47 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { fn::FieldEvaluator evaluator{context, &mask}; evaluator.add(positions_field_); evaluator.add(group_field_); - evaluator.add(search_group_field_); evaluator.evaluate(); const VArray &positions = evaluator.get_evaluated(0); const VArray &group = evaluator.get_evaluated(1); - const VArray &search_group = evaluator.get_evaluated(2); - const bool use_group = !group.is_single(); - const bool use_search_group = !search_group.is_single(); + MultiValueMap group_masks; + mask.foreach_index([&](const int index) { group_masks.add(group[index], index); }); - MultiValueMap in_group; - MultiValueMap out_group; Array indices(mask.min_array_size()); - threading::parallel_invoke( - (indices.size() > 512) && use_search_group && use_group, - [&]() { - if (use_group) { - mask.foreach_index([&](const auto index) { in_group.add(group[index], index); }); - return; - } - const int group_key = group.get_internal_single(); - in_group.add_multiple(group_key, {}); - }, - [&]() { - if (use_search_group) { - mask.foreach_index( - [&](const auto index) { out_group.add(search_group[index], index); }); - } + const auto nearest_for = [this, &positions](const IndexMask mask, MutableSpan r_indices) { + devirtualize_varray(positions, [mask, r_indices, this](const auto positions) { + KDTree_3d *tree = BLI_kdtree_3d_new(mask.size()); + mask.foreach_index([tree, positions](const int index) { + BLI_kdtree_3d_insert(tree, index, positions[index]); }); - for (const int key : in_group.keys()) { - /* Never empty. */ - const IndexMask self_points(use_group ? IndexMask(in_group.lookup(key)) : mask); - const IndexMask search_points(use_search_group ? IndexMask(out_group.lookup(key)) : - self_points); + BLI_kdtree_3d_balance(tree); - if (search_points.is_empty()) { - indices.as_mutable_span().fill_indices(self_points, -1); - continue; - } + threading::parallel_for(mask.index_range(), 512, [&](const IndexRange range) { + mask.slice(range).foreach_index([&](const auto index) { + r_indices[index] = this->kdtree_find_neighboard(tree, positions[index], index); + }); + }); - KDTree_3d *tree = BLI_kdtree_3d_new(search_points.size()); - - for (const int64_t index : search_points) { - BLI_kdtree_3d_insert(tree, index, positions[index]); - } - - BLI_kdtree_3d_balance(tree); - - threading::parallel_for(self_points.index_range(), 512, [&](const IndexRange range) { - for (const int64_t index : self_points.slice(range)) { - indices[index] = this->kdtree_find_neighboard(tree, positions[index], index); - } + BLI_kdtree_3d_free(tree); }); + }; - BLI_kdtree_3d_free(tree); + for (const Span mask_span : group_masks.values()) { + if (mask_span.size() == 1) { + indices[mask_span.first()] = -1; + } + nearest_for(mask_span, indices); } return VArray::ForContainer(std::move(indices)); } protected: - int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, const int &index) const + static int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, const int &index) { return BLI_kdtree_3d_find_nearest_cb_cpp( tree, @@ -126,12 +97,11 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { { positions_field_.node().for_each_field_input_recursive(fn); group_field_.node().for_each_field_input_recursive(fn); - search_group_field_.node().for_each_field_input_recursive(fn); } uint64_t hash() const override { - return get_default_hash_3(positions_field_, group_field_, search_group_field_); + return get_default_hash_2(positions_field_, group_field_); } bool is_equal_to(const fn::FieldNode &other) const override @@ -139,8 +109,7 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { if (const IndexOfNearestFieldInput *other_field = dynamic_cast(&other)) { return positions_field_ == other_field->positions_field_ && - group_field_ == other_field->group_field_ && - search_group_field_ == other_field->search_group_field_; + group_field_ == other_field->group_field_; } return false; } @@ -154,12 +123,10 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { static void node_geo_exec(GeoNodeExecParams params) { Field position_field = params.extract_input>("Position"); - - Field self_group_field = params.extract_input>("Self Group ID"); - Field search_group_field = params.extract_input>("Nearest Group ID"); + Field group_field = params.extract_input>("Group ID"); Field index_of_nearest_field(std::make_shared( - std::move(position_field), std::move(self_group_field), std::move(search_group_field))); + std::move(position_field), std::move(group_field))); if (params.output_is_required("Index")) { static auto clamp_fn = mf::build::SI1_SO( -- 2.30.2 From 3af5b2f7ff1f09d63945b92f29dffd659f77d0e6 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 20 Apr 2023 14:50:41 -0400 Subject: [PATCH 08/17] Move node to "Geometry > Sample" menu --- scripts/startup/bl_ui/node_add_menu_geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index e5a3a85af2d..d673f6941b2 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -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") @@ -528,7 +529,6 @@ class NODE_MT_category_GEO_UTILITIES_FIELD(Menu): node_add_menu.add_node_type(layout, "GeometryNodeAccumulateField") node_add_menu.add_node_type(layout, "GeometryNodeFieldAtIndex") node_add_menu.add_node_type(layout, "GeometryNodeFieldOnDomain") - node_add_menu.add_node_type(layout, "GeometryNodeIndexOfNearest") node_add_menu.draw_assets_for_catalog(layout, self.bl_label) -- 2.30.2 From 1c26c392e0c21d42d9ab2371069c2991556c1c27 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 20 Apr 2023 14:57:48 -0400 Subject: [PATCH 09/17] Also mention similar node in "Sample Nearest" node --- source/blender/nodes/NOD_static_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index eba6393110a..1bd9896261e 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -399,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") -- 2.30.2 From f8d47be90010de788202984f18a12aae48579db7 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 20 Apr 2023 15:00:46 -0400 Subject: [PATCH 10/17] Changes to implementation: - Build the kdtree from the entire input, not using the evaluation mask - Use separate static functions for some logic - Use VArraySpan for positions - Separate Has Neighbor to a different field input --- .../nodes/node_geo_index_of_nearest.cc | 243 ++++++++++++------ 1 file changed, 171 insertions(+), 72 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index 820f5219685..c2f624d462a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BKE_attribute_math.hh" - #include "BLI_kdtree.h" #include "BLI_multi_value_map.hh" #include "BLI_task.hh" @@ -19,6 +17,55 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Has Neighbor")).field_source(); } +static KDTree_3d *build_kdtree(const Span &positions, const IndexMask mask) +{ + KDTree_3d *tree = BLI_kdtree_3d_new(mask.size()); + for (const int i : mask) { + BLI_kdtree_3d_insert(tree, i, positions[i]); + } + BLI_kdtree_3d_balance(tree); + return tree; +} + +static int find_nearest_non_self(const KDTree_3d &tree, const float3 &position, const int index) +{ + 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 positions, + const IndexMask mask, + MutableSpan indices) +{ + threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { + for (const int i : mask.slice(range)) { + indices[i] = find_nearest_non_self(tree, positions[i], i); + } + }); +} + +static Vector masks_from_group_ids(const Span group_ids, + const IndexMask mask, + MultiValueMap &storage) +{ + mask.foreach_index([&](const int i) { storage.add(group_ids[i], i); }); + Vector masks; + masks.reserve(storage.size()); + for (const Span indices : storage.values()) { + masks.append(indices); + } + return masks; +} + +static Vector masks_from_group_ids(const Span group_ids, + MultiValueMap &storage) +{ + return masks_from_group_ids(group_ids, group_ids.index_range(), storage); +} + class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { private: const Field positions_field_; @@ -26,70 +73,70 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { public: IndexOfNearestFieldInput(Field positions_field, Field group_field) - : bke::GeometryFieldInput(CPPType::get(), "Nearest to"), + : bke::GeometryFieldInput(CPPType::get(), "Index of Nearest"), positions_field_(std::move(positions_field)), group_field_(std::move(group_field)) { } GVArray get_varray_for_context(const bke::GeometryFieldContext &context, - IndexMask mask) const final + const IndexMask mask) const final { - fn::FieldEvaluator evaluator{context, &mask}; + if (!context.attributes()) { + return {}; + } + const int domain_size = context.attributes()->domain_size(context.domain()); + fn::FieldEvaluator evaluator{context, domain_size}; evaluator.add(positions_field_); evaluator.add(group_field_); evaluator.evaluate(); + const VArraySpan positions = evaluator.get_evaluated(0); + const VArray group = evaluator.get_evaluated(1); - const VArray &positions = evaluator.get_evaluated(0); - const VArray &group = evaluator.get_evaluated(1); + Array result(mask.min_array_size()); - MultiValueMap group_masks; - mask.foreach_index([&](const int index) { group_masks.add(group[index], index); }); - - Array indices(mask.min_array_size()); - - const auto nearest_for = [this, &positions](const IndexMask mask, MutableSpan r_indices) { - devirtualize_varray(positions, [mask, r_indices, this](const auto positions) { - KDTree_3d *tree = BLI_kdtree_3d_new(mask.size()); - mask.foreach_index([tree, positions](const int index) { - BLI_kdtree_3d_insert(tree, index, positions[index]); - }); - - BLI_kdtree_3d_balance(tree); - - threading::parallel_for(mask.index_range(), 512, [&](const IndexRange range) { - mask.slice(range).foreach_index([&](const auto index) { - r_indices[index] = this->kdtree_find_neighboard(tree, positions[index], index); - }); - }); - - BLI_kdtree_3d_free(tree); - }); - }; - - for (const Span mask_span : group_masks.values()) { - if (mask_span.size() == 1) { - indices[mask_span.first()] = -1; - } - nearest_for(mask_span, indices); + if (group.is_single()) { + const IndexMask full_mask = positions.index_range(); + KDTree_3d *tree = build_kdtree(positions, full_mask); + find_neighbors(*tree, positions, mask, result); + BLI_kdtree_3d_free(tree); } + else { + /* The goal is to build each tree and use it immediately, rather than building all trees and + * sampling them later. That should help to keep the tree in caches before balancing and when + * sampling many points. */ + const VArraySpan group_ids(group); + MultiValueMap group_mask_storage; + const Vector tree_masks = masks_from_group_ids(group_ids, group_mask_storage); - return VArray::ForContainer(std::move(indices)); - } + MultiValueMap evaluate_masks_storage; + Vector evaluate_masks; + if (mask.size() < domain_size) { + /* Separate masks for evaluation are only necessary if the mask mask + * for field input evaluation doesn't have every element selected. */ + evaluate_masks = masks_from_group_ids(group_ids, mask, evaluate_masks_storage); + } - protected: - static int kdtree_find_neighboard(KDTree_3d *tree, const float3 &position, const int &index) - { - return BLI_kdtree_3d_find_nearest_cb_cpp( - tree, - position, - 0, - [index](const int other_new_i, const float * /*co*/, const float /*dist_sq*/) { - if (index == other_new_i) { - return 0; + /* The grain size should be larger as each tree gets smaller. */ + const int avg_tree_size = group_ids.size() / group_mask_storage.size(); + const int grain_size = std::max(8192 / avg_tree_size, 1); + threading::parallel_for(tree_masks.index_range(), grain_size, [&](const IndexRange range) { + for (const int i : range) { + const IndexMask tree_mask = tree_masks[i]; + const IndexMask evaluate_mask = evaluate_masks.is_empty() ? tree_mask : + evaluate_masks[i]; + if (tree_masks[i].size() < 2) { + result.as_mutable_span().fill_indices(evaluate_mask.indices(), 0); } - return 1; - }); + else { + KDTree_3d *tree = build_kdtree(positions, tree_mask); + find_neighbors(*tree, positions, evaluate_mask, result); + BLI_kdtree_3d_free(tree); + } + } + }); + } + return VArray::ForContainer(std::move(result)); } public: @@ -99,53 +146,105 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { group_field_.node().for_each_field_input_recursive(fn); } - uint64_t hash() const override + uint64_t hash() const final { return get_default_hash_2(positions_field_, group_field_); } - bool is_equal_to(const fn::FieldNode &other) const override + bool is_equal_to(const fn::FieldNode &other) const final { - if (const IndexOfNearestFieldInput *other_field = - dynamic_cast(&other)) { + if (const auto *other_field = dynamic_cast(&other)) { return positions_field_ == other_field->positions_field_ && group_field_ == other_field->group_field_; } return false; } - std::optional preferred_domain(const GeometryComponent &component) const override + std::optional 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 group_field_; + + public: + HasNeighborFieldInput(Field group_field) + : bke::GeometryFieldInput(CPPType::get(), "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()); + fn::FieldEvaluator evaluator{context, domain_size}; + evaluator.add(group_field_); + evaluator.evaluate(); + const VArray group = evaluator.get_evaluated(0); + + if (group.is_single()) { + return VArray::ForSingle(true, mask.min_array_size()); + } + + /* When a group ID is contained in the set, it means there is only one element with that ID. */ + Map counts; + const VArraySpan group_span(group); + mask.foreach_index([&](const int i) { + counts.add_or_modify( + group_span[i], [](int *count) { *count = 0; }, [](int *count) { (*count)++; }); + }); + Array result(mask.min_array_size()); + mask.foreach_index([&](const int i) { result[i] = counts.lookup(group_span[i]) > 1; }); + return VArray::ForContainer(std::move(result)); + } + + public: + void for_each_field_input_recursive(FunctionRef 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(&other)) { + return group_field_ == other_field->group_field_; + } + return false; + } + + std::optional preferred_domain(const GeometryComponent &component) const final + { + return bke::try_detect_field_domain(component, group_field_); + } +}; + static void node_geo_exec(GeoNodeExecParams params) { Field position_field = params.extract_input>("Position"); Field group_field = params.extract_input>("Group ID"); - Field index_of_nearest_field(std::make_shared( - std::move(position_field), std::move(group_field))); - if (params.output_is_required("Index")) { - static auto clamp_fn = mf::build::SI1_SO( - "Index Clamping", - [](const int index) { return math::max(0, index); }, - mf::build::exec_presets::Materialized()); - auto clamp_op = std::make_shared( - FieldOperation(std::move(clamp_fn), {index_of_nearest_field})); - params.set_output("Index", Field(clamp_op, 0)); + params.set_output("Index", + Field(std::make_shared( + std::move(position_field), group_field))); } if (params.output_is_required("Has Neighbor")) { - static auto valid_fn = mf::build::SI1_SO( - "Index Validating", - [](const int index) { return index != -1; }, - mf::build::exec_presets::Materialized()); - auto valid_op = std::make_shared( - FieldOperation(std::move(valid_fn), {std::move(index_of_nearest_field)})); - params.set_output("Has Neighbor", Field(valid_op, 0)); + params.set_output( + "Has Neighbor", + Field(std::make_shared(std::move(group_field)))); } } -- 2.30.2 From ecabb72a3ce1f3bc62a15200753f263fd4ef467f Mon Sep 17 00:00:00 2001 From: illua1 Date: Thu, 20 Apr 2023 22:51:24 +0300 Subject: [PATCH 11/17] progress --- .../nodes/node_geo_index_of_nearest.cc | 79 +++++++++---------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index c2f624d462a..a63a0e49f94 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -20,9 +20,8 @@ static void node_declare(NodeDeclarationBuilder &b) static KDTree_3d *build_kdtree(const Span &positions, const IndexMask mask) { KDTree_3d *tree = BLI_kdtree_3d_new(mask.size()); - for (const int i : mask) { - BLI_kdtree_3d_insert(tree, i, positions[i]); - } + mask.foreach_index( + [tree, positions](const int index) { BLI_kdtree_3d_insert(tree, i, positions[i]); }); BLI_kdtree_3d_balance(tree); return tree; } @@ -41,9 +40,9 @@ static void find_neighbors(const KDTree_3d &tree, MutableSpan indices) { threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { - for (const int i : mask.slice(range)) { + mask.foreach_index([tree, positions](const int index) { indices[i] = find_nearest_non_self(tree, positions[i], i); - } + }); }); } @@ -60,12 +59,6 @@ static Vector masks_from_group_ids(const Span group_ids, return masks; } -static Vector masks_from_group_ids(const Span group_ids, - MultiValueMap &storage) -{ - return masks_from_group_ids(group_ids, group_ids.index_range(), storage); -} - class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { private: const Field positions_field_; @@ -100,42 +93,42 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { KDTree_3d *tree = build_kdtree(positions, full_mask); find_neighbors(*tree, positions, mask, result); BLI_kdtree_3d_free(tree); + return VArray::ForContainer(std::move(result)); } - else { - /* The goal is to build each tree and use it immediately, rather than building all trees and - * sampling them later. That should help to keep the tree in caches before balancing and when - * sampling many points. */ - const VArraySpan group_ids(group); - MultiValueMap group_mask_storage; - const Vector tree_masks = masks_from_group_ids(group_ids, group_mask_storage); - MultiValueMap evaluate_masks_storage; - Vector evaluate_masks; - if (mask.size() < domain_size) { - /* Separate masks for evaluation are only necessary if the mask mask - * for field input evaluation doesn't have every element selected. */ - evaluate_masks = masks_from_group_ids(group_ids, mask, evaluate_masks_storage); - } + /* The goal is to build each tree and use it immediately, rather than building all trees and + * sampling them later. That should help to keep the tree in caches before balancing and when + * sampling many points. */ + const VArraySpan group_ids(group); + MultiValueMap group_mask_storage; + const Vector tree_masks = masks_from_group_ids( + group_ids, group_ids.index_mask(), group_mask_storage); - /* The grain size should be larger as each tree gets smaller. */ - const int avg_tree_size = group_ids.size() / group_mask_storage.size(); - const int grain_size = std::max(8192 / avg_tree_size, 1); - threading::parallel_for(tree_masks.index_range(), grain_size, [&](const IndexRange range) { - for (const int i : range) { - const IndexMask tree_mask = tree_masks[i]; - const IndexMask evaluate_mask = evaluate_masks.is_empty() ? tree_mask : - evaluate_masks[i]; - if (tree_masks[i].size() < 2) { - result.as_mutable_span().fill_indices(evaluate_mask.indices(), 0); - } - else { - KDTree_3d *tree = build_kdtree(positions, tree_mask); - find_neighbors(*tree, positions, evaluate_mask, result); - BLI_kdtree_3d_free(tree); - } + MultiValueMap evaluate_masks_storage; + Vector evaluate_masks; + if (mask.size() < domain_size) { + /* Separate masks for evaluation are only necessary if the mask mask + * for field input evaluation doesn't have every element selected. */ + evaluate_masks = masks_from_group_ids(group_ids, mask, evaluate_masks_storage); + } + + /* The grain size should be larger as each tree gets smaller. */ + const int avg_tree_size = group_ids.size() / group_mask_storage.size(); + const int grain_size = std::max(8192 / avg_tree_size, 1); + threading::parallel_for(tree_masks.index_range(), grain_size, [&](const IndexRange range) { + for (const int i : range) { + const IndexMask tree_mask = tree_masks[i]; + const IndexMask evaluate_mask = evaluate_masks.is_empty() ? tree_mask : evaluate_masks[i]; + if (tree_masks[i].size() < 2) { + result.as_mutable_span().fill_indices(evaluate_mask.indices(), 0); } - }); - } + else { + KDTree_3d *tree = build_kdtree(positions, tree_mask); + find_neighbors(*tree, positions, evaluate_mask, result); + BLI_kdtree_3d_free(tree); + } + } + }); return VArray::ForContainer(std::move(result)); } -- 2.30.2 From ce1deccee5299bd9480e86b6a2c2a285cdab8a31 Mon Sep 17 00:00:00 2001 From: illua1 Date: Fri, 21 Apr 2023 01:00:08 +0300 Subject: [PATCH 12/17] progress --- .../nodes/node_geo_index_of_nearest.cc | 138 ++++++++++-------- 1 file changed, 78 insertions(+), 60 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index a63a0e49f94..f7ac686bf21 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_kdtree.h" -#include "BLI_multi_value_map.hh" +#include "BLI_map.hh" #include "BLI_task.hh" +#include "BLI_vector.hh" #include "node_geometry_util.hh" @@ -17,11 +18,22 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Has Neighbor")).field_source(); } -static KDTree_3d *build_kdtree(const Span &positions, const IndexMask mask) +static KDTree_3d *build_kdtree(const Span &positions, const Span indices) { - KDTree_3d *tree = BLI_kdtree_3d_new(mask.size()); - mask.foreach_index( - [tree, positions](const int index) { BLI_kdtree_3d_insert(tree, i, positions[i]); }); + KDTree_3d *tree = BLI_kdtree_3d_new(indices.size()); + for (const int index : indices) { + BLI_kdtree_3d_insert(tree, index, positions[index]); + } + BLI_kdtree_3d_balance(tree); + return tree; +} + +static KDTree_3d *build_kdtree(const Span &positions, const IndexRange range) +{ + KDTree_3d *tree = BLI_kdtree_3d_new(range.size()); + for (const int index : range) { + BLI_kdtree_3d_insert(tree, index, positions[index]); + } BLI_kdtree_3d_balance(tree); return tree; } @@ -36,27 +48,25 @@ static int find_nearest_non_self(const KDTree_3d &tree, const float3 &position, static void find_neighbors(const KDTree_3d &tree, const Span positions, - const IndexMask mask, - MutableSpan indices) + const Span mask, + MutableSpan r_indices) { - threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { - mask.foreach_index([tree, positions](const int index) { - indices[i] = find_nearest_non_self(tree, positions[i], i); - }); + 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); + } }); } - -static Vector masks_from_group_ids(const Span group_ids, - const IndexMask mask, - MultiValueMap &storage) +static void find_neighbors(const KDTree_3d &tree, + const Span positions, + const IndexMask mask, + MutableSpan r_indices) { - mask.foreach_index([&](const int i) { storage.add(group_ids[i], i); }); - Vector masks; - masks.reserve(storage.size()); - for (const Span indices : storage.values()) { - masks.append(indices); - } - return masks; + threading::parallel_for(mask.index_range(), 512, [&](const IndexRange range) { + mask.slice(range).foreach_index([&tree, positions, r_indices](const int index) { + r_indices[index] = find_nearest_non_self(tree, positions[index], index); + }); + }); } class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { @@ -89,46 +99,53 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { Array result(mask.min_array_size()); if (group.is_single()) { - const IndexMask full_mask = positions.index_range(); - KDTree_3d *tree = build_kdtree(positions, full_mask); + KDTree_3d *tree = build_kdtree(positions, IndexRange(domain_size)); find_neighbors(*tree, positions, mask, result); BLI_kdtree_3d_free(tree); return VArray::ForContainer(std::move(result)); } - /* The goal is to build each tree and use it immediately, rather than building all trees and - * sampling them later. That should help to keep the tree in caches before balancing and when - * sampling many points. */ - const VArraySpan group_ids(group); - MultiValueMap group_mask_storage; - const Vector tree_masks = masks_from_group_ids( - group_ids, group_ids.index_mask(), group_mask_storage); + VectorSet group_indexing; + Vector> mask_indices; + Vector> tree_indices; + Vector forest; - MultiValueMap evaluate_masks_storage; - Vector evaluate_masks; - if (mask.size() < domain_size) { - /* Separate masks for evaluation are only necessary if the mask mask - * for field input evaluation doesn't have every element selected. */ - evaluate_masks = masks_from_group_ids(group_ids, mask, evaluate_masks_storage); + for (const int index : mask) { + const int group_id = group[index]; + const int index_of_group = group_indexing.index_of_or_add(group_id); + if (index_of_group == mask_indices.size()) { + mask_indices.append({}); + } + mask_indices[index_of_group].append(index); } - /* The grain size should be larger as each tree gets smaller. */ - const int avg_tree_size = group_ids.size() / group_mask_storage.size(); - const int grain_size = std::max(8192 / avg_tree_size, 1); - threading::parallel_for(tree_masks.index_range(), grain_size, [&](const IndexRange range) { - for (const int i : range) { - const IndexMask tree_mask = tree_masks[i]; - const IndexMask evaluate_mask = evaluate_masks.is_empty() ? tree_mask : evaluate_masks[i]; - if (tree_masks[i].size() < 2) { - result.as_mutable_span().fill_indices(evaluate_mask.indices(), 0); - } - else { - KDTree_3d *tree = build_kdtree(positions, tree_mask); - find_neighbors(*tree, positions, evaluate_mask, result); - BLI_kdtree_3d_free(tree); - } + tree_indices.resize(group_indexing.size()); + forest.resize(group_indexing.size()); + + for (const int index : IndexMask(domain_size)) { + const int group_id = group[index]; + const int index_of_group = group_indexing.index_of_try(group_id); + if (index_of_group != -1) { + tree_indices[index_of_group].append(index); + } + } + + threading::parallel_for(group_indexing.index_range(), 8, [&](const IndexRange range) { + for (const int index : range) { + const Span mask_of_tree = tree_indices[index]; + forest[index] = build_kdtree(positions, mask_of_tree); } }); + + threading::parallel_for(group_indexing.index_range(), 1024, [&](const IndexRange range) { + for (const int index : range) { + KDTree_3d &tree = *forest[index]; + const Span mask = mask_indices[index]; + find_neighbors(tree, positions, mask, result); + BLI_kdtree_3d_free(&tree); + } + }); + return VArray::ForContainer(std::move(result)); } @@ -158,7 +175,7 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { return bke::try_detect_field_domain(component, positions_field_); } }; - +/* class HasNeighborFieldInput final : public bke::GeometryFieldInput { private: const Field group_field_; @@ -186,7 +203,7 @@ class HasNeighborFieldInput final : public bke::GeometryFieldInput { return VArray::ForSingle(true, mask.min_array_size()); } - /* When a group ID is contained in the set, it means there is only one element with that ID. */ + /* When a group ID is contained in the set, it means there is only one element with that ID. *//* Map counts; const VArraySpan group_span(group); mask.foreach_index([&](const int i) { @@ -222,6 +239,7 @@ class HasNeighborFieldInput final : public bke::GeometryFieldInput { return bke::try_detect_field_domain(component, group_field_); } }; +*/ static void node_geo_exec(GeoNodeExecParams params) { @@ -233,12 +251,12 @@ static void node_geo_exec(GeoNodeExecParams params) Field(std::make_shared( std::move(position_field), group_field))); } - - if (params.output_is_required("Has Neighbor")) { - params.set_output( - "Has Neighbor", - Field(std::make_shared(std::move(group_field)))); - } + /* + if (params.output_is_required("Has Neighbor")) { + params.set_output( + "Has Neighbor", + Field(std::make_shared(std::move(group_field)))); + }*/ } } // namespace blender::nodes::node_geo_index_of_nearest_cc -- 2.30.2 From 61628dd06e051621840e646ff5c3509bd64af3e3 Mon Sep 17 00:00:00 2001 From: illua1 Date: Fri, 21 Apr 2023 15:02:26 +0300 Subject: [PATCH 13/17] progress --- source/blender/blenlib/BLI_kdtree_impl.h | 6 +- .../nodes/node_geo_index_of_nearest.cc | 67 +++++++++---------- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/source/blender/blenlib/BLI_kdtree_impl.h b/source/blender/blenlib/BLI_kdtree_impl.h index 4e01048c5d5..e42032e6402 100644 --- a/source/blender/blenlib/BLI_kdtree_impl.h +++ b/source/blender/blenlib/BLI_kdtree_impl.h @@ -110,16 +110,16 @@ template inline int BLI_kdtree_nd_(find_nearest_cb_cpp)(const KDTree *tree, const float co[KD_DIMS], KDTreeNearest *r_nearest, - const Fn &fn) + Fn &&fn) { return BLI_kdtree_nd_(find_nearest_cb)( tree, co, [](void *user_data, const int index, const float *co, const float dist_sq) { - const Fn &fn = *static_cast(user_data); + Fn &fn = *static_cast(user_data); return fn(index, co, dist_sq); }, - const_cast(&fn), + &fn, r_nearest); } #endif diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index f7ac686bf21..2d65427f35d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -63,9 +63,9 @@ static void find_neighbors(const KDTree_3d &tree, MutableSpan r_indices) { threading::parallel_for(mask.index_range(), 512, [&](const IndexRange range) { - mask.slice(range).foreach_index([&tree, positions, r_indices](const int index) { + for (const int index : mask.slice(range)) { r_indices[index] = find_nearest_non_self(tree, positions[index], index); - }); + } }); } @@ -106,41 +106,35 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { } VectorSet group_indexing; - Vector> mask_indices; - Vector> tree_indices; - Vector forest; for (const int index : mask) { const int group_id = group[index]; - const int index_of_group = group_indexing.index_of_or_add(group_id); - if (index_of_group == mask_indices.size()) { - mask_indices.append({}); - } - mask_indices[index_of_group].append(index); + group_indexing.add(group_id); } - tree_indices.resize(group_indexing.size()); - forest.resize(group_indexing.size()); + Vector> mask_indices(group_indexing.size()); + Vector> tree_indices(group_indexing.size()); - for (const int index : IndexMask(domain_size)) { - const int group_id = group[index]; - const int index_of_group = group_indexing.index_of_try(group_id); - if (index_of_group != -1) { - tree_indices[index_of_group].append(index); + const auto build_group_masks = [&](const IndexMask mask, MutableSpan> r_groups) { + for (const int index : mask) { + const int group_id = group[index]; + const int index_of_group = group_indexing.index_of_try(group_id); + if (index_of_group != -1) { + r_groups[index_of_group].append(index); + } } - } + }; - threading::parallel_for(group_indexing.index_range(), 8, [&](const IndexRange range) { + threading::parallel_invoke( + mask.size() + domain_size > 1024, + [&]() { build_group_masks(mask, mask_indices); }, + [&]() { build_group_masks(IndexMask(domain_size), tree_indices); }); + + threading::parallel_for(group_indexing.index_range(), 256, [&](const IndexRange range) { for (const int index : range) { const Span mask_of_tree = tree_indices[index]; - forest[index] = build_kdtree(positions, mask_of_tree); - } - }); - - threading::parallel_for(group_indexing.index_range(), 1024, [&](const IndexRange range) { - for (const int index : range) { - KDTree_3d &tree = *forest[index]; const Span mask = mask_indices[index]; + KDTree_3d &tree = *build_kdtree(positions, mask_of_tree); find_neighbors(tree, positions, mask, result); BLI_kdtree_3d_free(&tree); } @@ -175,7 +169,7 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { return bke::try_detect_field_domain(component, positions_field_); } }; -/* + class HasNeighborFieldInput final : public bke::GeometryFieldInput { private: const Field group_field_; @@ -194,6 +188,10 @@ class HasNeighborFieldInput final : public bke::GeometryFieldInput { return {}; } const int domain_size = context.attributes()->domain_size(context.domain()); + if (domain_size == 1) { + return VArray::ForSingle(false, mask.min_array_size()); + } + fn::FieldEvaluator evaluator{context, domain_size}; evaluator.add(group_field_); evaluator.evaluate(); @@ -203,7 +201,7 @@ class HasNeighborFieldInput final : public bke::GeometryFieldInput { return VArray::ForSingle(true, mask.min_array_size()); } - /* When a group ID is contained in the set, it means there is only one element with that ID. *//* + /* When a group ID is contained in the set, it means there is only one element with that ID. */ Map counts; const VArraySpan group_span(group); mask.foreach_index([&](const int i) { @@ -239,7 +237,6 @@ class HasNeighborFieldInput final : public bke::GeometryFieldInput { return bke::try_detect_field_domain(component, group_field_); } }; -*/ static void node_geo_exec(GeoNodeExecParams params) { @@ -251,12 +248,12 @@ static void node_geo_exec(GeoNodeExecParams params) Field(std::make_shared( std::move(position_field), group_field))); } - /* - if (params.output_is_required("Has Neighbor")) { - params.set_output( - "Has Neighbor", - Field(std::make_shared(std::move(group_field)))); - }*/ + + if (params.output_is_required("Has Neighbor")) { + params.set_output( + "Has Neighbor", + Field(std::make_shared(std::move(group_field)))); + } } } // namespace blender::nodes::node_geo_index_of_nearest_cc -- 2.30.2 From 590a9c07815195ede164b1bcbf88a1721590a182 Mon Sep 17 00:00:00 2001 From: illua1 Date: Fri, 21 Apr 2023 15:51:04 +0300 Subject: [PATCH 14/17] progress --- .../nodes/node_geo_index_of_nearest.cc | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index 2d65427f35d..ef11d005c1c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -1,9 +1,9 @@ /* 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 "BLI_vector.hh" #include "node_geometry_util.hh" @@ -18,20 +18,10 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Has Neighbor")).field_source(); } -static KDTree_3d *build_kdtree(const Span &positions, const Span indices) +static KDTree_3d *build_kdtree(const Span positions, const IndexMask mask) { - KDTree_3d *tree = BLI_kdtree_3d_new(indices.size()); - for (const int index : indices) { - BLI_kdtree_3d_insert(tree, index, positions[index]); - } - BLI_kdtree_3d_balance(tree); - return tree; -} - -static KDTree_3d *build_kdtree(const Span &positions, const IndexRange range) -{ - KDTree_3d *tree = BLI_kdtree_3d_new(range.size()); - for (const int index : range) { + KDTree_3d *tree = BLI_kdtree_3d_new(mask.size()); + for (const int index : mask) { BLI_kdtree_3d_insert(tree, index, positions[index]); } BLI_kdtree_3d_balance(tree); @@ -46,17 +36,6 @@ static int find_nearest_non_self(const KDTree_3d &tree, const float3 &position, }); } -static void find_neighbors(const KDTree_3d &tree, - const Span positions, - const Span mask, - MutableSpan 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); - } - }); -} static void find_neighbors(const KDTree_3d &tree, const Span positions, const IndexMask mask, @@ -112,10 +91,16 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { group_indexing.add(group_id); } - Vector> mask_indices(group_indexing.size()); - Vector> tree_indices(group_indexing.size()); + const bool mask_is_cheap = mask.size() < domain_size / 2; - const auto build_group_masks = [&](const IndexMask mask, MutableSpan> r_groups) { + Array> tree_indices(group_indexing.size()); + Array> mask_indices; + if (mask_is_cheap) { + mask_indices.reinitialize(group_indexing.size()); + } + + const auto build_group_masks = [&](const IndexMask mask, + MutableSpan> r_groups) { for (const int index : mask) { const int group_id = group[index]; const int index_of_group = group_indexing.index_of_try(group_id); @@ -126,15 +111,19 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { }; threading::parallel_invoke( - mask.size() + domain_size > 1024, - [&]() { build_group_masks(mask, mask_indices); }, + domain_size > 1024 && mask_is_cheap, + [&]() { + if (mask_is_cheap) { + build_group_masks(mask, mask_indices); + } + }, [&]() { build_group_masks(IndexMask(domain_size), tree_indices); }); threading::parallel_for(group_indexing.index_range(), 256, [&](const IndexRange range) { for (const int index : range) { - const Span mask_of_tree = tree_indices[index]; - const Span mask = mask_indices[index]; + const Span mask_of_tree = tree_indices[index]; KDTree_3d &tree = *build_kdtree(positions, mask_of_tree); + const Span mask = mask_is_cheap ? mask_indices[index].as_span() : mask_of_tree; find_neighbors(tree, positions, mask, result); BLI_kdtree_3d_free(&tree); } @@ -201,7 +190,6 @@ class HasNeighborFieldInput final : public bke::GeometryFieldInput { return VArray::ForSingle(true, mask.min_array_size()); } - /* When a group ID is contained in the set, it means there is only one element with that ID. */ Map counts; const VArraySpan group_span(group); mask.foreach_index([&](const int i) { -- 2.30.2 From ae4cf3861e0cfcefdc39bfbb1af5b23376e77394 Mon Sep 17 00:00:00 2001 From: illua1 Date: Fri, 21 Apr 2023 16:21:47 +0300 Subject: [PATCH 15/17] progress --- .../nodes/node_geo_index_of_nearest.cc | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index ef11d005c1c..80ba303829d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -75,9 +75,10 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { const VArraySpan positions = evaluator.get_evaluated(0); const VArray group = evaluator.get_evaluated(1); - Array result(mask.min_array_size()); + Array result; if (group.is_single()) { + 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); @@ -91,13 +92,18 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { group_indexing.add(group_id); } - const bool mask_is_cheap = mask.size() < domain_size / 2; + const bool use_cheap_mask = mask.size() < domain_size / 2; Array> tree_indices(group_indexing.size()); Array> mask_indices; - if (mask_is_cheap) { + + if (use_cheap_mask) { + result.reinitialize(mask.min_array_size()); mask_indices.reinitialize(group_indexing.size()); } + else { + result.reinitialize(domain_size); + } const auto build_group_masks = [&](const IndexMask mask, MutableSpan> r_groups) { @@ -111,9 +117,9 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { }; threading::parallel_invoke( - domain_size > 1024 && mask_is_cheap, + domain_size > 1024 && use_cheap_mask, [&]() { - if (mask_is_cheap) { + if (use_cheap_mask) { build_group_masks(mask, mask_indices); } }, @@ -123,7 +129,7 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { for (const int index : range) { const Span mask_of_tree = tree_indices[index]; KDTree_3d &tree = *build_kdtree(positions, mask_of_tree); - const Span mask = mask_is_cheap ? mask_indices[index].as_span() : mask_of_tree; + const Span mask = use_cheap_mask ? mask_indices[index].as_span() : mask_of_tree; find_neighbors(tree, positions, mask, result); BLI_kdtree_3d_free(&tree); } -- 2.30.2 From 76022550de8ffb8047da8e8c79c0d68d4271ce93 Mon Sep 17 00:00:00 2001 From: illua1 Date: Fri, 21 Apr 2023 17:28:02 +0300 Subject: [PATCH 16/17] progress --- .../nodes/geometry/nodes/node_geo_index_of_nearest.cc | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index 80ba303829d..35e12afbfda 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -86,7 +86,6 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { } VectorSet group_indexing; - for (const int index : mask) { const int group_id = group[index]; group_indexing.add(group_id); @@ -127,11 +126,11 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { threading::parallel_for(group_indexing.index_range(), 256, [&](const IndexRange range) { for (const int index : range) { - const Span mask_of_tree = tree_indices[index]; - KDTree_3d &tree = *build_kdtree(positions, mask_of_tree); - const Span mask = use_cheap_mask ? mask_indices[index].as_span() : mask_of_tree; - find_neighbors(tree, positions, mask, result); - BLI_kdtree_3d_free(&tree); + const IndexMask mask_of_tree = tree_indices[index].as_span(); + const IndexMask mask = use_cheap_mask ? IndexMask(mask_indices[index]) : mask_of_tree; + KDTree_3d *tree = build_kdtree(positions, mask_of_tree); + find_neighbors(*tree, positions, mask, result); + BLI_kdtree_3d_free(tree); } }); -- 2.30.2 From afcc40fb4aa1cc4b56928a3e5c9c59e2640c3c1f Mon Sep 17 00:00:00 2001 From: illua1 Date: Sat, 22 Apr 2023 12:59:57 +0300 Subject: [PATCH 17/17] Jacques name changes --- .../nodes/node_geo_index_of_nearest.cc | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc index 35e12afbfda..c03d8de174b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_of_nearest.cc @@ -91,14 +91,18 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { group_indexing.add(group_id); } - const bool use_cheap_mask = mask.size() < domain_size / 2; + /* Each group id has two corresponding index masks. One that contains all the points in the + * group, one that contains all the points in the group that should be looked up (this is the + * 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; - Array> tree_indices(group_indexing.size()); - Array> mask_indices; + Array> all_indices_by_group_id(group_indexing.size()); + Array> lookup_indices_by_group_id; - if (use_cheap_mask) { + if (use_separate_lookup_indices) { result.reinitialize(mask.min_array_size()); - mask_indices.reinitialize(group_indexing.size()); + lookup_indices_by_group_id.reinitialize(group_indexing.size()); } else { result.reinitialize(domain_size); @@ -116,20 +120,22 @@ class IndexOfNearestFieldInput final : public bke::GeometryFieldInput { }; threading::parallel_invoke( - domain_size > 1024 && use_cheap_mask, + domain_size > 1024 && use_separate_lookup_indices, [&]() { - if (use_cheap_mask) { - build_group_masks(mask, mask_indices); + if (use_separate_lookup_indices) { + build_group_masks(mask, lookup_indices_by_group_id); } }, - [&]() { build_group_masks(IndexMask(domain_size), tree_indices); }); + [&]() { build_group_masks(IndexMask(domain_size), all_indices_by_group_id); }); threading::parallel_for(group_indexing.index_range(), 256, [&](const IndexRange range) { for (const int index : range) { - const IndexMask mask_of_tree = tree_indices[index].as_span(); - const IndexMask mask = use_cheap_mask ? IndexMask(mask_indices[index]) : mask_of_tree; - KDTree_3d *tree = build_kdtree(positions, mask_of_tree); - find_neighbors(*tree, positions, mask, result); + const IndexMask tree_mask = all_indices_by_group_id[index].as_span(); + 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); } }); -- 2.30.2