diff --git a/source/blender/blenkernel/BKE_bvhutils.hh b/source/blender/blenkernel/BKE_bvhutils.hh index 55dc86d6923..84906820738 100644 --- a/source/blender/blenkernel/BKE_bvhutils.hh +++ b/source/blender/blenkernel/BKE_bvhutils.hh @@ -12,6 +12,7 @@ #include #include "BLI_bit_span.hh" +#include "BLI_index_mask_fwd.hh" #include "BLI_kdopbvh.h" #include "BLI_math_vector_types.hh" #include "BLI_span.hh" @@ -114,6 +115,13 @@ BVHTree *BKE_bvhtree_from_mesh_get(BVHTreeFromMesh *data, BVHCacheType bvh_cache_type, int tree_type); +/** + * Build a bvh tree from the triangles in the mesh that correspond to the faces in the given mask. + */ +void BKE_bvhtree_from_mesh_tris_init(const Mesh &mesh, + const blender::IndexMask &faces_mask, + BVHTreeFromMesh &r_data); + /** * Frees data allocated by a call to `bvhtree_from_mesh_*`. */ diff --git a/source/blender/blenkernel/BKE_mesh.hh b/source/blender/blenkernel/BKE_mesh.hh index 924e6c7e6f1..8de79f0737c 100644 --- a/source/blender/blenkernel/BKE_mesh.hh +++ b/source/blender/blenkernel/BKE_mesh.hh @@ -290,6 +290,17 @@ inline int face_triangles_num(const int face_size) return face_size - 2; } +/** + * Return the range of triangles that belong to the given face. + */ +inline IndexRange face_triangles_range(OffsetIndices faces, int face_i) +{ + const IndexRange face = faces[face_i]; + /* This is the same as #poly_to_tri_count which is not included here. */ + const int start_triangle = face.start() - face_i * 2; + return IndexRange(start_triangle, face_triangles_num(face.size())); +} + /** * Return the index of the edge's vertex that is not the \a vert. */ diff --git a/source/blender/blenkernel/intern/bvhutils.cc b/source/blender/blenkernel/intern/bvhutils.cc index e7604d498ea..9e96822501e 100644 --- a/source/blender/blenkernel/intern/bvhutils.cc +++ b/source/blender/blenkernel/intern/bvhutils.cc @@ -932,6 +932,53 @@ BVHTree *BKE_bvhtree_from_mesh_get(BVHTreeFromMesh *data, return data->tree; } +void BKE_bvhtree_from_mesh_tris_init(const Mesh &mesh, + const blender::IndexMask &faces_mask, + BVHTreeFromMesh &r_data) +{ + using namespace blender; + using namespace blender::bke; + + const Span positions = mesh.vert_positions(); + const Span edges = mesh.edges(); + const Span corner_verts = mesh.corner_verts(); + const OffsetIndices faces = mesh.faces(); + const Span corner_tris = mesh.corner_tris(); + bvhtree_from_mesh_setup_data(nullptr, + BVHTREE_FROM_CORNER_TRIS, + positions, + edges, + corner_verts, + corner_tris, + nullptr, + &r_data); + + int tris_num = 0; + faces_mask.foreach_index( + [&](const int i) { tris_num += mesh::face_triangles_num(faces[i].size()); }); + + int active_num = -1; + BVHTree *tree = bvhtree_new_common(0.0f, 2, 6, tris_num, active_num); + r_data.tree = tree; + if (tree == nullptr) { + return; + } + + faces_mask.foreach_index([&](const int face_i) { + const IndexRange triangles_range = mesh::face_triangles_range(faces, face_i); + for (const int tri_i : triangles_range) { + float co[3][3]; + copy_v3_v3(co[0], positions[corner_verts[corner_tris[tri_i][0]]]); + copy_v3_v3(co[1], positions[corner_verts[corner_tris[tri_i][1]]]); + copy_v3_v3(co[2], positions[corner_verts[corner_tris[tri_i][2]]]); + + BLI_bvhtree_insert(tree, tri_i, co[0], 3); + } + }); + + BLI_bvhtree_balance(tree); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h index 24dff6b3cc4..014e4ad4393 100644 --- a/source/blender/makesdna/DNA_meshdata_types.h +++ b/source/blender/makesdna/DNA_meshdata_types.h @@ -82,9 +82,7 @@ enum { * }; * * // Access all triangles in a given face. - * const IndexRange face = faces[i]; - * const Span corner_tris = corner_tris.slice(poly_to_tri_count(i, face.start()), - * bke::mesh::face_triangles_num(face.size())); + * const Span corner_tris = corner_tris.slice(face_triangles_range(faces, i)); * \endcode * * It may also be useful to check whether or not two vertices of a triangle form an edge in the diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc index 805efb40ff2..54fc8c108f8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc @@ -14,6 +14,8 @@ #include "RNA_enum_types.hh" +#include "BLI_task.hh" + #include "node_geometry_util.hh" namespace blender::nodes::node_geo_sample_nearest_surface_cc { @@ -29,12 +31,22 @@ static void node_declare(NodeDeclarationBuilder &b) const eCustomDataType data_type = eCustomDataType(node->custom1); b.add_input(data_type, "Value").hide_value().field_on_all(); } + b.add_input("Group ID") + .hide_value() + .field_on_all() + .description( + "Splits the faces of the input mesh into groups which can be sampled individually"); b.add_input("Sample Position").implicit_field(implicit_field_inputs::position); + b.add_input("Sample Group ID").hide_value().supports_field(); if (node != nullptr) { const eCustomDataType data_type = eCustomDataType(node->custom1); - b.add_output(data_type, "Value").dependent_field({2}); + b.add_output(data_type, "Value").dependent_field({3, 4}); } + b.add_output("Is Valid") + .dependent_field({3, 4}) + .description( + "Whether the sampling was successfull. It can fail when the sampled group is empty"); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) @@ -64,46 +76,105 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) } } -static void get_closest_mesh_tris(const Mesh &mesh, - const VArray &positions, - const IndexMask &mask, - const MutableSpan r_tri_indices, - const MutableSpan r_distances_sq, - const MutableSpan r_positions) -{ - BLI_assert(mesh.faces_num > 0); - BVHTreeFromMesh tree_data; - BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_CORNER_TRIS, 2); - get_closest_in_bvhtree(tree_data, positions, mask, r_tri_indices, r_distances_sq, r_positions); - free_bvhtree_from_mesh(&tree_data); -} - class SampleNearestSurfaceFunction : public mf::MultiFunction { + private: GeometrySet source_; + Array bvh_trees_; + VectorSet group_indices_; public: - SampleNearestSurfaceFunction(GeometrySet geometry) : source_(std::move(geometry)) + SampleNearestSurfaceFunction(GeometrySet geometry, const Field &group_id_field) + : source_(std::move(geometry)) { source_.ensure_owns_direct_data(); static const mf::Signature signature = []() { mf::Signature signature; mf::SignatureBuilder builder{"Sample Nearest Surface", signature}; builder.single_input("Position"); + builder.single_input("Sample ID"); builder.single_output("Triangle Index"); builder.single_output("Sample Position"); + builder.single_output("Is Valid", mf::ParamFlag::SupportsUnusedOutput); return signature; }(); this->set_signature(&signature); + + const Mesh &mesh = *source_.get_mesh(); + + /* Compute group ids on mesh. */ + bke::MeshFieldContext field_context{mesh, bke::AttrDomain::Face}; + FieldEvaluator field_evaluator{field_context, mesh.faces_num}; + field_evaluator.add(group_id_field); + field_evaluator.evaluate(); + VArraySpan group_ids_span = field_evaluator.get_evaluated(0); + + /* Compute an #IndexMask for every unique group id. */ + group_indices_.add_multiple(group_ids_span); + const int groups_num = group_indices_.size(); + IndexMaskMemory memory; + Array group_masks(groups_num); + IndexMask::from_groups( + IndexMask(mesh.faces_num), + memory, + [&](const int i) { return group_indices_.index_of(group_ids_span[i]); }, + group_masks); + + /* Construct BVH tree for each group. */ + bvh_trees_.reinitialize(groups_num); + threading::parallel_for(IndexRange(groups_num), 16, [&](const IndexRange range) { + for (const int group_i : range) { + const IndexMask &group_mask = group_masks[group_i]; + BVHTreeFromMesh &bvh = bvh_trees_[group_i]; + if (group_mask.size() == mesh.faces_num) { + BKE_bvhtree_from_mesh_get(&bvh, &mesh, BVHTREE_FROM_CORNER_TRIS, 2); + } + else { + BKE_bvhtree_from_mesh_tris_init(mesh, group_mask, bvh); + } + } + }); + } + + ~SampleNearestSurfaceFunction() + { + for (BVHTreeFromMesh &tree : bvh_trees_) { + free_bvhtree_from_mesh(&tree); + } } void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override { const VArray &positions = params.readonly_single_input(0, "Position"); - MutableSpan triangle_index = params.uninitialized_single_output(1, "Triangle Index"); + const VArray &sample_ids = params.readonly_single_input(1, "Sample ID"); + MutableSpan triangle_index = params.uninitialized_single_output(2, "Triangle Index"); MutableSpan sample_position = params.uninitialized_single_output( - 2, "Sample Position"); - const Mesh &mesh = *source_.get_mesh(); - get_closest_mesh_tris(mesh, positions, mask, triangle_index, {}, sample_position); + 3, "Sample Position"); + MutableSpan is_valid_span = params.uninitialized_single_output_if_required( + 4, "Is Valid"); + + mask.foreach_index([&](const int i) { + const float3 position = positions[i]; + const int sample_id = sample_ids[i]; + const int group_index = group_indices_.index_of_try(sample_id); + if (group_index == -1) { + triangle_index[i] = -1; + sample_position[i] = float3(0, 0, 0); + if (!is_valid_span.is_empty()) { + is_valid_span[i] = false; + } + return; + } + const BVHTreeFromMesh &bvh = bvh_trees_[group_index]; + BVHTreeNearest nearest; + nearest.dist_sq = FLT_MAX; + BLI_bvhtree_find_nearest( + bvh.tree, position, &nearest, bvh.nearest_callback, const_cast(&bvh)); + triangle_index[i] = nearest.index; + sample_position[i] = nearest.co; + if (!is_valid_span.is_empty()) { + is_valid_span[i] = true; + } + }); } ExecutionHints get_execution_hints() const override @@ -133,10 +204,13 @@ static void node_geo_exec(GeoNodeExecParams params) } auto nearest_op = FieldOperation::Create( - std::make_shared(geometry), - {params.extract_input>("Sample Position")}); + std::make_shared(geometry, + params.extract_input>("Group ID")), + {params.extract_input>("Sample Position"), + params.extract_input>("Sample Group ID")}); Field triangle_indices(nearest_op, 0); Field nearest_positions(nearest_op, 1); + Field is_valid(nearest_op, 2); Field bary_weights = Field(FieldOperation::Create( std::make_shared(geometry), @@ -148,6 +222,7 @@ static void node_geo_exec(GeoNodeExecParams params) {triangle_indices, bary_weights}); params.set_output("Value", GField(sample_op)); + params.set_output("Is Valid", is_valid); } static void node_rna(StructRNA *srna)