Geometry Nodes: support group ids in Sample Nearest Surface node
This adds group ids to the `Sample Nearest Surface` node. This allows e.g. finding the closest point on a specific mesh island. Three new sockets are added: * `Group ID`: Is evaluated on the face domain and splits the input mesh into multiple parts, each with its own id. * `Sample Group ID`: Determines in which group the closest nearest surface is detected. * `Is Valid`: Outputs true if a nearest surface was found, it's false if the group is empty. Pull Request: https://projects.blender.org/blender/blender/pulls/118150
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#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_*`.
|
||||
*/
|
||||
|
@@ -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<int> 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.
|
||||
*/
|
||||
|
@@ -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<float3> positions = mesh.vert_positions();
|
||||
const Span<int2> edges = mesh.edges();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
const OffsetIndices faces = mesh.faces();
|
||||
const Span<int3> 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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@@ -82,9 +82,7 @@ enum {
|
||||
* };
|
||||
*
|
||||
* // Access all triangles in a given face.
|
||||
* const IndexRange face = faces[i];
|
||||
* const Span<int3> corner_tris = corner_tris.slice(poly_to_tri_count(i, face.start()),
|
||||
* bke::mesh::face_triangles_num(face.size()));
|
||||
* const Span<int3> 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
|
||||
|
@@ -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<decl::Int>("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<decl::Vector>("Sample Position").implicit_field(implicit_field_inputs::position);
|
||||
b.add_input<decl::Int>("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<decl::Bool>("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<float3> &positions,
|
||||
const IndexMask &mask,
|
||||
const MutableSpan<int> r_tri_indices,
|
||||
const MutableSpan<float> r_distances_sq,
|
||||
const MutableSpan<float3> 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<BVHTreeFromMesh> bvh_trees_;
|
||||
VectorSet<int> group_indices_;
|
||||
|
||||
public:
|
||||
SampleNearestSurfaceFunction(GeometrySet geometry) : source_(std::move(geometry))
|
||||
SampleNearestSurfaceFunction(GeometrySet geometry, const Field<int> &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<float3>("Position");
|
||||
builder.single_input<int>("Sample ID");
|
||||
builder.single_output<int>("Triangle Index");
|
||||
builder.single_output<float3>("Sample Position");
|
||||
builder.single_output<bool>("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<int> group_ids_span = field_evaluator.get_evaluated<int>(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<IndexMask> group_masks(groups_num);
|
||||
IndexMask::from_groups<int>(
|
||||
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<float3> &positions = params.readonly_single_input<float3>(0, "Position");
|
||||
MutableSpan<int> triangle_index = params.uninitialized_single_output<int>(1, "Triangle Index");
|
||||
const VArray<int> &sample_ids = params.readonly_single_input<int>(1, "Sample ID");
|
||||
MutableSpan<int> triangle_index = params.uninitialized_single_output<int>(2, "Triangle Index");
|
||||
MutableSpan<float3> sample_position = params.uninitialized_single_output<float3>(
|
||||
2, "Sample Position");
|
||||
const Mesh &mesh = *source_.get_mesh();
|
||||
get_closest_mesh_tris(mesh, positions, mask, triangle_index, {}, sample_position);
|
||||
3, "Sample Position");
|
||||
MutableSpan<bool> is_valid_span = params.uninitialized_single_output_if_required<bool>(
|
||||
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<BVHTreeFromMesh *>(&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<SampleNearestSurfaceFunction>(geometry),
|
||||
{params.extract_input<Field<float3>>("Sample Position")});
|
||||
std::make_shared<SampleNearestSurfaceFunction>(geometry,
|
||||
params.extract_input<Field<int>>("Group ID")),
|
||||
{params.extract_input<Field<float3>>("Sample Position"),
|
||||
params.extract_input<Field<int>>("Sample Group ID")});
|
||||
Field<int> triangle_indices(nearest_op, 0);
|
||||
Field<float3> nearest_positions(nearest_op, 1);
|
||||
Field<bool> is_valid(nearest_op, 2);
|
||||
|
||||
Field<float3> bary_weights = Field<float3>(FieldOperation::Create(
|
||||
std::make_shared<bke::mesh_surface_sample::BaryWeightFromPositionFn>(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)
|
||||
|
Reference in New Issue
Block a user