Cleanup: Unify mesh sampling multifunctions
Change the implementation of the raycast and sample nearest surface node to split separate loops into separate multi-functions. This clarifies the task of each function, gives more information to the field evaluator, and gives more opportunity for memory reuse. Sampling mesh attributes with triangle barycentric weights is now implemented in a single place. Two other new multi-functions handle conversion of sampled positions into barycentric weights. Normalizing the ray directions for the raycast node is split out too, so it can be skipped in some cases in the future. The mesh attribute interpolator helper class is also removed, since it didn't give much benefit over a more functional approach. I didn't notice a performance improvement from this change. Pull Request: https://projects.blender.org/blender/blender/pulls/107563
This commit is contained in:
@@ -10,9 +10,13 @@
|
||||
#include "BLI_generic_virtual_array.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_field.hh"
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
#include "DNA_meshdata_types.h"
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
#include "BKE_geometry_fields.hh"
|
||||
|
||||
struct Mesh;
|
||||
struct BVHTreeFromMesh;
|
||||
@@ -51,43 +55,6 @@ void sample_face_attribute(Span<MLoopTri> looptris,
|
||||
IndexMask mask,
|
||||
GMutableSpan dst);
|
||||
|
||||
enum class eAttributeMapMode {
|
||||
INTERPOLATED,
|
||||
NEAREST,
|
||||
};
|
||||
|
||||
/**
|
||||
* A utility class that performs attribute interpolation from a source mesh.
|
||||
*
|
||||
* The interpolator is only valid as long as the mesh is valid.
|
||||
* Barycentric weights are needed when interpolating point or corner domain attributes,
|
||||
* these are computed lazily when needed and re-used.
|
||||
*/
|
||||
class MeshAttributeInterpolator {
|
||||
const Mesh *mesh_;
|
||||
const IndexMask mask_;
|
||||
const Span<float3> positions_;
|
||||
const Span<int> looptri_indices_;
|
||||
|
||||
Array<float3> bary_coords_;
|
||||
Array<float3> nearest_weights_;
|
||||
|
||||
public:
|
||||
MeshAttributeInterpolator(const Mesh *mesh,
|
||||
IndexMask mask,
|
||||
Span<float3> positions,
|
||||
Span<int> looptri_indices);
|
||||
|
||||
void sample_data(const GVArray &src,
|
||||
eAttrDomain domain,
|
||||
eAttributeMapMode mode,
|
||||
GMutableSpan dst);
|
||||
|
||||
protected:
|
||||
Span<float3> ensure_barycentric_coords();
|
||||
Span<float3> ensure_nearest_weights();
|
||||
};
|
||||
|
||||
/**
|
||||
* Find randomly distributed points on the surface of a mesh within a 3D sphere. This does not
|
||||
* sample an exact number of points because it comes with extra overhead to avoid bias that is only
|
||||
@@ -162,4 +129,56 @@ inline T sample_corner_attribute_with_bary_coords(const float3 &bary_weights,
|
||||
corner_attribute[looptri.tri[2]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate barycentric weights from triangle indices and positions within the triangles.
|
||||
*/
|
||||
class BaryWeightFromPositionFn : public mf::MultiFunction {
|
||||
GeometrySet source_;
|
||||
Span<float3> vert_positions_;
|
||||
Span<int> corner_verts_;
|
||||
Span<MLoopTri> looptris_;
|
||||
|
||||
public:
|
||||
BaryWeightFromPositionFn(GeometrySet geometry);
|
||||
void call(IndexMask mask, mf::Params params, mf::Context context) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate face corner weights from triangle indices and positions within the triangles.
|
||||
* The weights are 1 for the nearest corner and 0 for the two others.
|
||||
*/
|
||||
class CornerBaryWeightFromPositionFn : public mf::MultiFunction {
|
||||
GeometrySet source_;
|
||||
Span<float3> vert_positions_;
|
||||
Span<int> corner_verts_;
|
||||
Span<MLoopTri> looptris_;
|
||||
|
||||
public:
|
||||
CornerBaryWeightFromPositionFn(GeometrySet geometry);
|
||||
void call(IndexMask mask, mf::Params params, mf::Context context) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluate an attribute on the input geometry and sample it with input barycentric weights and
|
||||
* triangle indices.
|
||||
*/
|
||||
class BaryWeightSampleFn : public mf::MultiFunction {
|
||||
mf::Signature signature_;
|
||||
|
||||
GeometrySet source_;
|
||||
Span<MLoopTri> looptris_;
|
||||
std::optional<bke::MeshFieldContext> source_context_;
|
||||
std::unique_ptr<fn::FieldEvaluator> source_evaluator_;
|
||||
const GVArray *source_data_;
|
||||
eAttrDomain domain_;
|
||||
|
||||
public:
|
||||
BaryWeightSampleFn(GeometrySet geometry, fn::GField src_field);
|
||||
|
||||
void call(IndexMask mask, mf::Params params, mf::Context context) const;
|
||||
|
||||
private:
|
||||
void evaluate_source(fn::GField src_field);
|
||||
};
|
||||
|
||||
} // namespace blender::bke::mesh_surface_sample
|
||||
|
||||
@@ -55,7 +55,7 @@ void sample_point_attribute(const Span<int> corner_verts,
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename T, bool check_indices = false>
|
||||
BLI_NOINLINE static void sample_corner_attribute(const Span<MLoopTri> looptris,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<float3> bary_coords,
|
||||
@@ -64,6 +64,12 @@ BLI_NOINLINE static void sample_corner_attribute(const Span<MLoopTri> looptris,
|
||||
const MutableSpan<T> dst)
|
||||
{
|
||||
for (const int i : mask) {
|
||||
if constexpr (check_indices) {
|
||||
if (looptri_indices[i] == -1) {
|
||||
dst[i] = {};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const MLoopTri &tri = looptris[looptri_indices[i]];
|
||||
dst[i] = sample_corner_attribute_with_bary_coords(bary_coords[i], tri, src);
|
||||
}
|
||||
@@ -130,99 +136,52 @@ void sample_face_attribute(const Span<MLoopTri> looptris,
|
||||
});
|
||||
}
|
||||
|
||||
MeshAttributeInterpolator::MeshAttributeInterpolator(const Mesh *mesh,
|
||||
const IndexMask mask,
|
||||
const Span<float3> positions,
|
||||
const Span<int> looptri_indices)
|
||||
: mesh_(mesh), mask_(mask), positions_(positions), looptri_indices_(looptri_indices)
|
||||
template<bool check_indices = false>
|
||||
static void sample_barycentric_weights(const Span<float3> vert_positions,
|
||||
const Span<int> corner_verts,
|
||||
const Span<MLoopTri> looptris,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<float3> sample_positions,
|
||||
const IndexMask mask,
|
||||
MutableSpan<float3> bary_coords)
|
||||
{
|
||||
BLI_assert(positions.size() == looptri_indices.size());
|
||||
}
|
||||
|
||||
Span<float3> MeshAttributeInterpolator::ensure_barycentric_coords()
|
||||
{
|
||||
if (!bary_coords_.is_empty()) {
|
||||
BLI_assert(bary_coords_.size() >= mask_.min_array_size());
|
||||
return bary_coords_;
|
||||
}
|
||||
bary_coords_.reinitialize(mask_.min_array_size());
|
||||
|
||||
const Span<float3> positions = mesh_->vert_positions();
|
||||
const Span<int> corner_verts = mesh_->corner_verts();
|
||||
const Span<MLoopTri> looptris = mesh_->looptris();
|
||||
|
||||
for (const int i : mask_) {
|
||||
const MLoopTri &tri = looptris[looptri_indices_[i]];
|
||||
bary_coords_[i] = compute_bary_coord_in_triangle(positions, corner_verts, tri, positions_[i]);
|
||||
}
|
||||
return bary_coords_;
|
||||
}
|
||||
|
||||
Span<float3> MeshAttributeInterpolator::ensure_nearest_weights()
|
||||
{
|
||||
if (!nearest_weights_.is_empty()) {
|
||||
BLI_assert(nearest_weights_.size() >= mask_.min_array_size());
|
||||
return nearest_weights_;
|
||||
}
|
||||
nearest_weights_.reinitialize(mask_.min_array_size());
|
||||
|
||||
const Span<float3> positions = mesh_->vert_positions();
|
||||
const Span<int> corner_verts = mesh_->corner_verts();
|
||||
const Span<MLoopTri> looptris = mesh_->looptris();
|
||||
|
||||
for (const int i : mask_) {
|
||||
const int looptri_index = looptri_indices_[i];
|
||||
const MLoopTri &looptri = looptris[looptri_index];
|
||||
|
||||
const float d0 = len_squared_v3v3(positions_[i], positions[corner_verts[looptri.tri[0]]]);
|
||||
const float d1 = len_squared_v3v3(positions_[i], positions[corner_verts[looptri.tri[1]]]);
|
||||
const float d2 = len_squared_v3v3(positions_[i], positions[corner_verts[looptri.tri[2]]]);
|
||||
|
||||
nearest_weights_[i] = MIN3_PAIR(d0, d1, d2, float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1));
|
||||
}
|
||||
return nearest_weights_;
|
||||
}
|
||||
|
||||
void MeshAttributeInterpolator::sample_data(const GVArray &src,
|
||||
const eAttrDomain domain,
|
||||
const eAttributeMapMode mode,
|
||||
const GMutableSpan dst)
|
||||
{
|
||||
if (src.is_empty() || dst.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute barycentric coordinates only when they are needed. */
|
||||
Span<float3> weights;
|
||||
if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CORNER)) {
|
||||
switch (mode) {
|
||||
case eAttributeMapMode::INTERPOLATED:
|
||||
weights = this->ensure_barycentric_coords();
|
||||
break;
|
||||
case eAttributeMapMode::NEAREST:
|
||||
weights = this->ensure_nearest_weights();
|
||||
break;
|
||||
for (const int i : mask) {
|
||||
if constexpr (check_indices) {
|
||||
if (looptri_indices[i] == -1) {
|
||||
bary_coords[i] = {};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const MLoopTri &tri = looptris[looptri_indices[i]];
|
||||
bary_coords[i] = compute_bary_coord_in_triangle(
|
||||
vert_positions, corner_verts, tri, sample_positions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Interpolate the source attributes on the surface. */
|
||||
switch (domain) {
|
||||
case ATTR_DOMAIN_POINT:
|
||||
sample_point_attribute(
|
||||
mesh_->corner_verts(), mesh_->looptris(), looptri_indices_, weights, src, mask_, dst);
|
||||
break;
|
||||
case ATTR_DOMAIN_FACE:
|
||||
sample_face_attribute(mesh_->looptris(), looptri_indices_, src, mask_, dst);
|
||||
break;
|
||||
case ATTR_DOMAIN_CORNER:
|
||||
sample_corner_attribute(mesh_->looptris(), looptri_indices_, weights, src, mask_, dst);
|
||||
break;
|
||||
case ATTR_DOMAIN_EDGE:
|
||||
/* Not yet supported. */
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
template<bool check_indices = false>
|
||||
static void sample_nearest_weights(const Span<float3> vert_positions,
|
||||
const Span<int> corner_verts,
|
||||
const Span<MLoopTri> looptris,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<float3> sample_positions,
|
||||
const IndexMask mask,
|
||||
MutableSpan<float3> bary_coords)
|
||||
{
|
||||
for (const int i : mask) {
|
||||
if constexpr (check_indices) {
|
||||
if (looptri_indices[i] == -1) {
|
||||
bary_coords[i] = {};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const MLoopTri &tri = looptris[looptri_indices[i]];
|
||||
bary_coords[i] = MIN3_PAIR(
|
||||
math::distance_squared(sample_positions[i], vert_positions[corner_verts[tri.tri[0]]]),
|
||||
math::distance_squared(sample_positions[i], vert_positions[corner_verts[tri.tri[1]]]),
|
||||
math::distance_squared(sample_positions[i], vert_positions[corner_verts[tri.tri[2]]]),
|
||||
float3(1, 0, 0),
|
||||
float3(0, 1, 0),
|
||||
float3(0, 0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,4 +358,119 @@ float3 compute_bary_coord_in_triangle(const Span<float3> vert_positions,
|
||||
return bary_coords;
|
||||
}
|
||||
|
||||
BaryWeightFromPositionFn::BaryWeightFromPositionFn(GeometrySet geometry)
|
||||
: source_(std::move(geometry))
|
||||
{
|
||||
source_.ensure_owns_direct_data();
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Bary Weight from Position", signature};
|
||||
builder.single_input<float3>("Position");
|
||||
builder.single_input<int>("Triangle Index");
|
||||
builder.single_output<float3>("Barycentric Weight");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
const Mesh &mesh = *source_.get_mesh_for_read();
|
||||
vert_positions_ = mesh.vert_positions();
|
||||
corner_verts_ = mesh.corner_verts();
|
||||
looptris_ = mesh.looptris();
|
||||
}
|
||||
|
||||
void BaryWeightFromPositionFn::call(IndexMask mask,
|
||||
mf::Params params,
|
||||
mf::Context /*context*/) const
|
||||
{
|
||||
const VArraySpan<float3> sample_positions = params.readonly_single_input<float3>(0, "Position");
|
||||
const VArraySpan<int> triangle_indices = params.readonly_single_input<int>(1, "Triangle Index");
|
||||
MutableSpan<float3> bary_weights = params.uninitialized_single_output<float3>(
|
||||
2, "Barycentric Weight");
|
||||
sample_barycentric_weights<true>(vert_positions_,
|
||||
corner_verts_,
|
||||
looptris_,
|
||||
triangle_indices,
|
||||
sample_positions,
|
||||
mask,
|
||||
bary_weights);
|
||||
}
|
||||
|
||||
CornerBaryWeightFromPositionFn::CornerBaryWeightFromPositionFn(GeometrySet geometry)
|
||||
: source_(std::move(geometry))
|
||||
{
|
||||
source_.ensure_owns_direct_data();
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Nearest Weight from Position", signature};
|
||||
builder.single_input<float3>("Position");
|
||||
builder.single_input<int>("Triangle Index");
|
||||
builder.single_output<float3>("Barycentric Weight");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
const Mesh &mesh = *source_.get_mesh_for_read();
|
||||
vert_positions_ = mesh.vert_positions();
|
||||
corner_verts_ = mesh.corner_verts();
|
||||
looptris_ = mesh.looptris();
|
||||
}
|
||||
|
||||
void CornerBaryWeightFromPositionFn::call(IndexMask mask,
|
||||
mf::Params params,
|
||||
mf::Context /*context*/) const
|
||||
{
|
||||
const VArraySpan<float3> sample_positions = params.readonly_single_input<float3>(0, "Position");
|
||||
const VArraySpan<int> triangle_indices = params.readonly_single_input<int>(1, "Triangle Index");
|
||||
MutableSpan<float3> bary_weights = params.uninitialized_single_output<float3>(
|
||||
2, "Barycentric Weight");
|
||||
sample_nearest_weights<true>(vert_positions_,
|
||||
corner_verts_,
|
||||
looptris_,
|
||||
triangle_indices,
|
||||
sample_positions,
|
||||
mask,
|
||||
bary_weights);
|
||||
}
|
||||
|
||||
BaryWeightSampleFn::BaryWeightSampleFn(GeometrySet geometry, fn::GField src_field)
|
||||
: source_(std::move(geometry))
|
||||
{
|
||||
source_.ensure_owns_direct_data();
|
||||
this->evaluate_source(std::move(src_field));
|
||||
mf::SignatureBuilder builder{"Sample Barycentric Triangles", signature_};
|
||||
builder.single_input<int>("Triangle Index");
|
||||
builder.single_input<float3>("Barycentric Weight");
|
||||
builder.single_output("Value", source_data_->type());
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void BaryWeightSampleFn::call(const IndexMask mask,
|
||||
mf::Params params,
|
||||
mf::Context /*context*/) const
|
||||
{
|
||||
const VArraySpan<int> triangle_indices = params.readonly_single_input<int>(0, "Triangle Index");
|
||||
const VArraySpan<float3> bary_weights = params.readonly_single_input<float3>(
|
||||
1, "Barycentric Weight");
|
||||
GMutableSpan dst = params.uninitialized_single_output(2, "Value");
|
||||
attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
sample_corner_attribute<T, true>(
|
||||
looptris_, triangle_indices, bary_weights, source_data_->typed<T>(), mask, dst.typed<T>());
|
||||
});
|
||||
}
|
||||
|
||||
void BaryWeightSampleFn::evaluate_source(fn::GField src_field)
|
||||
{
|
||||
const Mesh &mesh = *source_.get_mesh_for_read();
|
||||
looptris_ = mesh.looptris();
|
||||
/* Use the most complex domain for now, ensuring no information is lost. In the future, it should
|
||||
* be possible to use the most complex domain required by the field inputs, to simplify sampling
|
||||
* and avoid domain conversions. */
|
||||
domain_ = ATTR_DOMAIN_CORNER;
|
||||
source_context_.emplace(bke::MeshFieldContext(mesh, domain_));
|
||||
const int domain_size = mesh.attributes().domain_size(domain_);
|
||||
source_evaluator_ = std::make_unique<fn::FieldEvaluator>(*source_context_, domain_size);
|
||||
source_evaluator_->add(std::move(src_field));
|
||||
source_evaluator_->evaluate();
|
||||
source_data_ = &source_evaluator_->get_evaluated(0);
|
||||
}
|
||||
|
||||
} // namespace blender::bke::mesh_surface_sample
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_index_mask_ops.hh"
|
||||
|
||||
#include "DNA_mesh_types.h"
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
@@ -116,17 +118,6 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
}
|
||||
}
|
||||
|
||||
static eAttributeMapMode get_map_mode(GeometryNodeRaycastMapMode map_mode)
|
||||
{
|
||||
switch (map_mode) {
|
||||
case GEO_NODE_RAYCAST_INTERPOLATED:
|
||||
return eAttributeMapMode::INTERPOLATED;
|
||||
default:
|
||||
case GEO_NODE_RAYCAST_NEAREST:
|
||||
return eAttributeMapMode::NEAREST;
|
||||
}
|
||||
}
|
||||
|
||||
static void raycast_to_mesh(IndexMask mask,
|
||||
const Mesh &mesh,
|
||||
const VArray<float3> &ray_origins,
|
||||
@@ -136,8 +127,7 @@ static void raycast_to_mesh(IndexMask mask,
|
||||
const MutableSpan<int> r_hit_indices,
|
||||
const MutableSpan<float3> r_hit_positions,
|
||||
const MutableSpan<float3> r_hit_normals,
|
||||
const MutableSpan<float> r_hit_distances,
|
||||
int &hit_count)
|
||||
const MutableSpan<float> r_hit_distances)
|
||||
{
|
||||
BVHTreeFromMesh tree_data;
|
||||
BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 4);
|
||||
@@ -152,7 +142,7 @@ static void raycast_to_mesh(IndexMask mask,
|
||||
for (const int i : mask) {
|
||||
const float ray_length = ray_lengths[i];
|
||||
const float3 ray_origin = ray_origins[i];
|
||||
const float3 ray_direction = math::normalize(ray_directions[i]);
|
||||
const float3 ray_direction = ray_directions[i];
|
||||
|
||||
BVHTreeRayHit hit;
|
||||
hit.index = -1;
|
||||
@@ -165,7 +155,6 @@ static void raycast_to_mesh(IndexMask mask,
|
||||
tree_data.raycast_callback,
|
||||
&tree_data) != -1)
|
||||
{
|
||||
hit_count++;
|
||||
if (!r_hit.is_empty()) {
|
||||
r_hit[i] = hit.index >= 0;
|
||||
}
|
||||
@@ -206,113 +195,42 @@ static void raycast_to_mesh(IndexMask mask,
|
||||
class RaycastFunction : public mf::MultiFunction {
|
||||
private:
|
||||
GeometrySet target_;
|
||||
GeometryNodeRaycastMapMode mapping_;
|
||||
|
||||
/** The field for data evaluated on the target geometry. */
|
||||
std::optional<bke::MeshFieldContext> target_context_;
|
||||
std::unique_ptr<FieldEvaluator> target_evaluator_;
|
||||
const GVArray *target_data_ = nullptr;
|
||||
|
||||
/* Always evaluate the target domain data on the face corner domain because it contains the most
|
||||
* information. Eventually this could be exposed as an option or determined automatically from
|
||||
* the field inputs for better performance. */
|
||||
const eAttrDomain domain_ = ATTR_DOMAIN_CORNER;
|
||||
|
||||
mf::Signature signature_;
|
||||
|
||||
public:
|
||||
RaycastFunction(GeometrySet target, GField src_field, GeometryNodeRaycastMapMode mapping)
|
||||
: target_(std::move(target)), mapping_((GeometryNodeRaycastMapMode)mapping)
|
||||
RaycastFunction(GeometrySet target) : target_(std::move(target))
|
||||
{
|
||||
target_.ensure_owns_direct_data();
|
||||
this->evaluate_target_field(std::move(src_field));
|
||||
|
||||
mf::SignatureBuilder builder{"Geometry Proximity", signature_};
|
||||
builder.single_input<float3>("Source Position");
|
||||
builder.single_input<float3>("Ray Direction");
|
||||
builder.single_input<float>("Ray Length");
|
||||
builder.single_output<bool>("Is Hit", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float3>("Hit Position");
|
||||
builder.single_output<float3>("Hit Normal", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Distance", mf::ParamFlag::SupportsUnusedOutput);
|
||||
if (target_data_) {
|
||||
builder.single_output(
|
||||
"Attribute", target_data_->type(), mf::ParamFlag::SupportsUnusedOutput);
|
||||
}
|
||||
this->set_signature(&signature_);
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Raycast", signature};
|
||||
builder.single_input<float3>("Source Position");
|
||||
builder.single_input<float3>("Ray Direction");
|
||||
builder.single_input<float>("Ray Length");
|
||||
builder.single_output<bool>("Is Hit", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float3>("Hit Position", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float3>("Hit Normal", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Distance", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<int>("Triangle Index", mf::ParamFlag::SupportsUnusedOutput);
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(IndexMask mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
/* Hit positions are always necessary for retrieving the attribute from the target if that
|
||||
* output is required, so always retrieve a span from the evaluator in that case (it's
|
||||
* expected that the evaluator is more likely to have a spare buffer that could be used). */
|
||||
MutableSpan<float3> hit_positions = params.uninitialized_single_output<float3>(4,
|
||||
"Hit Position");
|
||||
|
||||
Array<int> hit_indices;
|
||||
if (target_data_) {
|
||||
hit_indices.reinitialize(mask.min_array_size());
|
||||
}
|
||||
|
||||
BLI_assert(target_.has_mesh());
|
||||
const Mesh &mesh = *target_.get_mesh_for_read();
|
||||
|
||||
int hit_count = 0;
|
||||
raycast_to_mesh(mask,
|
||||
mesh,
|
||||
params.readonly_single_input<float3>(0, "Source Position"),
|
||||
params.readonly_single_input<float3>(1, "Ray Direction"),
|
||||
params.readonly_single_input<float>(2, "Ray Length"),
|
||||
params.uninitialized_single_output_if_required<bool>(3, "Is Hit"),
|
||||
hit_indices,
|
||||
hit_positions,
|
||||
params.uninitialized_single_output_if_required<int>(7, "Triangle Index"),
|
||||
params.uninitialized_single_output_if_required<float3>(4, "Hit Position"),
|
||||
params.uninitialized_single_output_if_required<float3>(5, "Hit Normal"),
|
||||
params.uninitialized_single_output_if_required<float>(6, "Distance"),
|
||||
hit_count);
|
||||
|
||||
if (target_data_) {
|
||||
IndexMask hit_mask;
|
||||
Vector<int64_t> hit_mask_indices;
|
||||
if (hit_count < mask.size()) {
|
||||
/* Not all rays hit the target. Create a corrected mask to avoid transferring attribute
|
||||
* data to invalid indices. An alternative would be handling -1 indices in a separate case
|
||||
* in #MeshAttributeInterpolator, but since it already has an IndexMask in its constructor,
|
||||
* it's simpler to use that. */
|
||||
hit_mask_indices.reserve(hit_count);
|
||||
for (const int64_t i : mask) {
|
||||
if (hit_indices[i] != -1) {
|
||||
hit_mask_indices.append(i);
|
||||
}
|
||||
hit_mask = IndexMask(hit_mask_indices);
|
||||
}
|
||||
}
|
||||
else {
|
||||
hit_mask = mask;
|
||||
}
|
||||
|
||||
GMutableSpan result = params.uninitialized_single_output_if_required(7, "Attribute");
|
||||
if (!result.is_empty()) {
|
||||
MeshAttributeInterpolator interp(&mesh, hit_mask, hit_positions, hit_indices);
|
||||
result.type().value_initialize_indices(result.data(), mask);
|
||||
interp.sample_data(*target_data_, domain_, get_map_mode(mapping_), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void evaluate_target_field(GField src_field)
|
||||
{
|
||||
if (!src_field) {
|
||||
return;
|
||||
}
|
||||
const Mesh &mesh = *target_.get_mesh_for_read();
|
||||
target_context_.emplace(bke::MeshFieldContext{mesh, domain_});
|
||||
const int domain_size = mesh.attributes().domain_size(domain_);
|
||||
target_evaluator_ = std::make_unique<FieldEvaluator>(*target_context_, domain_size);
|
||||
target_evaluator_->add(std::move(src_field));
|
||||
target_evaluator_->evaluate();
|
||||
target_data_ = &target_evaluator_->get_evaluated(0);
|
||||
params.uninitialized_single_output_if_required<float>(6, "Distance"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -401,23 +319,43 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
GField field = get_input_attribute_field(params, data_type);
|
||||
const bool do_attribute_transfer = bool(field);
|
||||
Field<float3> position_field = params.extract_input<Field<float3>>("Source Position");
|
||||
Field<float3> direction_field = params.extract_input<Field<float3>>("Ray Direction");
|
||||
Field<float> length_field = params.extract_input<Field<float>>("Ray Length");
|
||||
static auto normalize_fn = mf::build::SI1_SO<float3, float3>(
|
||||
"Normalize",
|
||||
[](const float3 &v) { return math::normalize(v); },
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
auto direction_op = FieldOperation::Create(
|
||||
normalize_fn, {params.extract_input<Field<float3>>("Ray Direction")});
|
||||
|
||||
auto fn = std::make_unique<RaycastFunction>(std::move(target), std::move(field), mapping);
|
||||
auto op = FieldOperation::Create(
|
||||
std::move(fn),
|
||||
{std::move(position_field), std::move(direction_field), std::move(length_field)});
|
||||
auto op = FieldOperation::Create(std::make_unique<RaycastFunction>(target),
|
||||
{params.extract_input<Field<float3>>("Source Position"),
|
||||
Field<float3>(direction_op),
|
||||
params.extract_input<Field<float>>("Ray Length")});
|
||||
|
||||
Field<float3> hit_position(op, 1);
|
||||
params.set_output("Is Hit", Field<bool>(op, 0));
|
||||
params.set_output("Hit Position", Field<float3>(op, 1));
|
||||
params.set_output("Hit Position", hit_position);
|
||||
params.set_output("Hit Normal", Field<float3>(op, 2));
|
||||
params.set_output("Hit Distance", Field<float>(op, 3));
|
||||
if (do_attribute_transfer) {
|
||||
output_attribute_field(params, GField(op, 4));
|
||||
|
||||
if (GField field = get_input_attribute_field(params, data_type)) {
|
||||
Field<int> triangle_index(op, 4);
|
||||
Field<float3> bary_weights;
|
||||
switch (mapping) {
|
||||
case GEO_NODE_RAYCAST_INTERPOLATED:
|
||||
bary_weights = Field<float3>(FieldOperation::Create(
|
||||
std::make_shared<bke::mesh_surface_sample::BaryWeightFromPositionFn>(target),
|
||||
{hit_position, triangle_index}));
|
||||
break;
|
||||
case GEO_NODE_RAYCAST_NEAREST:
|
||||
bary_weights = Field<float3>(FieldOperation::Create(
|
||||
std::make_shared<bke::mesh_surface_sample::CornerBaryWeightFromPositionFn>(target),
|
||||
{hit_position, triangle_index}));
|
||||
}
|
||||
auto sample_op = FieldOperation::Create(
|
||||
std::make_shared<bke::mesh_surface_sample::BaryWeightSampleFn>(std::move(target),
|
||||
std::move(field)),
|
||||
{triangle_index, bary_weights});
|
||||
output_attribute_field(params, GField(sample_op));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,71 +111,32 @@ static void get_closest_mesh_looptris(const Mesh &mesh,
|
||||
free_bvhtree_from_mesh(&tree_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* \note Multi-threading for this function is provided by the field evaluator. Since the #call
|
||||
* function could be called many times, calculate the data from the source geometry once and store
|
||||
* it for later.
|
||||
*/
|
||||
class SampleNearestSurfaceFunction : public mf::MultiFunction {
|
||||
GeometrySet source_;
|
||||
GField src_field_;
|
||||
|
||||
/**
|
||||
* This function is meant to sample the surface of a mesh rather than take the value from
|
||||
* individual elements, so use the most complex domain, ensuring no information is lost. In the
|
||||
* future, it should be possible to use the most complex domain required by the field inputs, to
|
||||
* simplify sampling and avoid domain conversions.
|
||||
*/
|
||||
eAttrDomain domain_ = ATTR_DOMAIN_CORNER;
|
||||
|
||||
mf::Signature signature_;
|
||||
|
||||
std::optional<bke::MeshFieldContext> source_context_;
|
||||
std::unique_ptr<FieldEvaluator> source_evaluator_;
|
||||
const GVArray *source_data_;
|
||||
|
||||
public:
|
||||
SampleNearestSurfaceFunction(GeometrySet geometry, GField src_field)
|
||||
: source_(std::move(geometry)), src_field_(std::move(src_field))
|
||||
SampleNearestSurfaceFunction(GeometrySet geometry) : source_(std::move(geometry))
|
||||
{
|
||||
source_.ensure_owns_direct_data();
|
||||
this->evaluate_source_field();
|
||||
|
||||
mf::SignatureBuilder builder{"Sample Nearest Surface", signature_};
|
||||
builder.single_input<float3>("Position");
|
||||
builder.single_output("Value", src_field_.cpp_type(), mf::ParamFlag::SupportsUnusedOutput);
|
||||
this->set_signature(&signature_);
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Sample Nearest Surface", signature};
|
||||
builder.single_input<float3>("Position");
|
||||
builder.single_output<int>("Triangle Index");
|
||||
builder.single_output<float3>("Sample Position");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(IndexMask mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray<float3> &positions = params.readonly_single_input<float3>(0, "Position");
|
||||
GMutableSpan dst = params.uninitialized_single_output_if_required(1, "Value");
|
||||
|
||||
const MeshComponent &mesh_component = *source_.get_component_for_read<MeshComponent>();
|
||||
BLI_assert(mesh_component.has_mesh());
|
||||
const Mesh &mesh = *mesh_component.get_for_read();
|
||||
BLI_assert(mesh.totpoly > 0);
|
||||
|
||||
/* Find closest points on the mesh surface. */
|
||||
Array<int> looptri_indices(mask.min_array_size());
|
||||
Array<float3> sampled_positions(mask.min_array_size());
|
||||
get_closest_mesh_looptris(mesh, positions, mask, looptri_indices, {}, sampled_positions);
|
||||
|
||||
MeshAttributeInterpolator interp(&mesh, mask, sampled_positions, looptri_indices);
|
||||
interp.sample_data(*source_data_, domain_, eAttributeMapMode::INTERPOLATED, dst);
|
||||
}
|
||||
|
||||
private:
|
||||
void evaluate_source_field()
|
||||
{
|
||||
MutableSpan<int> triangle_index = params.uninitialized_single_output<int>(1, "Triangle Index");
|
||||
MutableSpan<float3> sample_position = params.uninitialized_single_output<float3>(
|
||||
2, "Sample Position");
|
||||
const Mesh &mesh = *source_.get_mesh_for_read();
|
||||
source_context_.emplace(bke::MeshFieldContext{mesh, domain_});
|
||||
const int domain_size = mesh.attributes().domain_size(domain_);
|
||||
source_evaluator_ = std::make_unique<FieldEvaluator>(*source_context_, domain_size);
|
||||
source_evaluator_->add(src_field_);
|
||||
source_evaluator_->evaluate();
|
||||
source_data_ = &source_evaluator_->get_evaluated(0);
|
||||
get_closest_mesh_looptris(mesh, positions, mask, triangle_index, {}, sample_position);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -245,11 +206,22 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
Field<float3> positions = params.extract_input<Field<float3>>("Sample Position");
|
||||
auto nearest_op = FieldOperation::Create(
|
||||
std::make_shared<SampleNearestSurfaceFunction>(geometry),
|
||||
{params.extract_input<Field<float3>>("Sample Position")});
|
||||
Field<int> triangle_indices(nearest_op, 0);
|
||||
Field<float3> nearest_positions(nearest_op, 1);
|
||||
|
||||
Field<float3> bary_weights = Field<float3>(FieldOperation::Create(
|
||||
std::make_shared<bke::mesh_surface_sample::BaryWeightFromPositionFn>(geometry),
|
||||
{nearest_positions, triangle_indices}));
|
||||
|
||||
GField field = get_input_attribute_field(params, data_type);
|
||||
auto fn = std::make_shared<SampleNearestSurfaceFunction>(std::move(geometry), std::move(field));
|
||||
auto op = FieldOperation::Create(std::move(fn), {std::move(positions)});
|
||||
output_attribute_field(params, GField(std::move(op)));
|
||||
auto sample_op = FieldOperation::Create(
|
||||
std::make_shared<bke::mesh_surface_sample::BaryWeightSampleFn>(geometry, std::move(field)),
|
||||
{triangle_indices, bary_weights});
|
||||
|
||||
output_attribute_field(params, GField(sample_op));
|
||||
}
|
||||
|
||||
} // namespace blender::nodes::node_geo_sample_nearest_surface_cc
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_mesh_sample.hh"
|
||||
#include "BKE_type_conversions.hh"
|
||||
|
||||
#include "UI_interface.h"
|
||||
@@ -105,80 +106,6 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
}
|
||||
}
|
||||
|
||||
class SampleMeshBarycentricFunction : public mf::MultiFunction {
|
||||
GeometrySet source_;
|
||||
GField src_field_;
|
||||
|
||||
/**
|
||||
* Use the most complex domain for now ensuring no information is lost. In the future, it should
|
||||
* be possible to use the most complex domain required by the field inputs, to simplify sampling
|
||||
* and avoid domain conversions.
|
||||
*/
|
||||
eAttrDomain domain_ = ATTR_DOMAIN_CORNER;
|
||||
|
||||
mf::Signature signature_;
|
||||
|
||||
std::optional<bke::MeshFieldContext> source_context_;
|
||||
std::unique_ptr<FieldEvaluator> source_evaluator_;
|
||||
const GVArray *source_data_;
|
||||
|
||||
Span<MLoopTri> looptris_;
|
||||
|
||||
public:
|
||||
SampleMeshBarycentricFunction(GeometrySet geometry, GField src_field)
|
||||
: source_(std::move(geometry)), src_field_(std::move(src_field))
|
||||
{
|
||||
source_.ensure_owns_direct_data();
|
||||
this->evaluate_source();
|
||||
|
||||
mf::SignatureBuilder builder{"Sample Barycentric Triangles", signature_};
|
||||
builder.single_input<int>("Triangle Index");
|
||||
builder.single_input<float3>("Barycentric Weight");
|
||||
builder.single_output("Value", src_field_.cpp_type());
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(IndexMask mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArraySpan<int> triangle_indices = params.readonly_single_input<int>(0,
|
||||
"Triangle Index");
|
||||
const VArraySpan<float3> bary_weights = params.readonly_single_input<float3>(
|
||||
1, "Barycentric Weight");
|
||||
GMutableSpan dst = params.uninitialized_single_output(2, "Value");
|
||||
|
||||
attribute_math::convert_to_static_type(src_field_.cpp_type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
const VArray<T> src_typed = source_data_->typed<T>();
|
||||
MutableSpan<T> dst_typed = dst.typed<T>();
|
||||
for (const int i : mask) {
|
||||
const int triangle_index = triangle_indices[i];
|
||||
if (triangle_indices[i] != -1) {
|
||||
dst_typed[i] = attribute_math::mix3(bary_weights[i],
|
||||
src_typed[looptris_[triangle_index].tri[0]],
|
||||
src_typed[looptris_[triangle_index].tri[1]],
|
||||
src_typed[looptris_[triangle_index].tri[2]]);
|
||||
}
|
||||
else {
|
||||
dst_typed[i] = {};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
void evaluate_source()
|
||||
{
|
||||
const Mesh &mesh = *source_.get_mesh_for_read();
|
||||
looptris_ = mesh.looptris();
|
||||
source_context_.emplace(bke::MeshFieldContext{mesh, domain_});
|
||||
const int domain_size = mesh.attributes().domain_size(domain_);
|
||||
source_evaluator_ = std::make_unique<FieldEvaluator>(*source_context_, domain_size);
|
||||
source_evaluator_->add(src_field_);
|
||||
source_evaluator_->evaluate();
|
||||
source_data_ = &source_evaluator_->get_evaluated(0);
|
||||
}
|
||||
};
|
||||
|
||||
class ReverseUVSampleFunction : public mf::MultiFunction {
|
||||
GeometrySet source_;
|
||||
Field<float2> src_uv_map_field_;
|
||||
@@ -323,7 +250,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
/* Use the output of the UV sampling to interpolate the mesh attribute. */
|
||||
GField field = get_input_attribute_field(params, data_type);
|
||||
auto sample_op = FieldOperation::Create(
|
||||
std::make_shared<SampleMeshBarycentricFunction>(std::move(geometry), std::move(field)),
|
||||
std::make_shared<bke::mesh_surface_sample::BaryWeightSampleFn>(std::move(geometry),
|
||||
std::move(field)),
|
||||
{Field<int>(uv_op, 1), Field<float3>(uv_op, 2)});
|
||||
output_attribute_field(params, GField(sample_op, 0));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user