Geometry Nodes: Add index and value inputs to sample curve node

As described in T92474 and T91650, this patch adds two features to the
sample curve node. First is an index input, to allow choosing the curve
to sample for each point. Second is a custom field input, which is
evaluated on the control points of the curve and then sampled like the
other outputs. There is an "All Curves" option for the old behavior
which takes the length of all curves into account.

For invalid curve indices, the node outputs zeros (default values).
Invalid lengths and factors are clamped.

There have been various discussions about splitting the node up more,
but this is an intuitive combination of options and will work well
enough for current use cases. The node could still be generalized more
in the future.

Keep in mind that the source field is evaluated on curve control points,
not the evaluated points used for sampling. This is necessary so that
fields like "Index" work as expected.

Differential Revision: https://developer.blender.org/D16147
This commit is contained in:
Hans Goudey 2022-11-02 12:29:01 +01:00
parent 460c9d3d92
commit 5f7ca5462d
Notes: blender-bot 2023-02-13 17:36:44 +01:00
Referenced by issue #91650, Support sampling custom attributes in Sample Curve node
5 changed files with 310 additions and 71 deletions

View File

@ -25,7 +25,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 5
#define BLENDER_FILE_SUBVERSION 6
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file

View File

@ -3653,6 +3653,25 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 304, 6)) {
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type != NTREE_GEOMETRY) {
continue;
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type != GEO_NODE_SAMPLE_CURVE) {
continue;
}
static_cast<NodeGeometryCurveSample *>(node->storage)->use_all_curves = true;
static_cast<NodeGeometryCurveSample *>(node->storage)->data_type = CD_PROP_FLOAT;
bNodeSocket *curve_socket = nodeFindSocket(node, SOCK_IN, "Curve");
BLI_assert(curve_socket != nullptr);
STRNCPY(curve_socket->name, "Curves");
STRNCPY(curve_socket->identifier, "Curves");
}
}
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -1497,6 +1497,10 @@ typedef struct NodeGeometryCurveToPoints {
typedef struct NodeGeometryCurveSample {
/* GeometryNodeCurveSampleMode. */
uint8_t mode;
int8_t use_all_curves;
/* eCustomDataType. */
int8_t data_type;
char _pad[1];
} NodeGeometryCurveSample;
typedef struct NodeGeometryTransferAttribute {

View File

@ -9442,10 +9442,26 @@ static void def_geo_curve_sample(StructRNA *srna)
RNA_def_struct_sdna_from(srna, "NodeGeometryCurveSample", "storage");
PropertyRNA *prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
PropertyRNA *prop;
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, mode_items);
RNA_def_property_ui_text(prop, "Mode", "Method for sampling input");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
prop = RNA_def_property(srna, "use_all_curves", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(prop,
"All Curves",
"Sample lengths based on the total lengh of all curves, rather than "
"using a length inside each selected curve");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_attribute_type_items);
RNA_def_property_enum_funcs(
prop, NULL, NULL, "rna_GeometryNodeAttributeType_type_with_socket_itemf");
RNA_def_property_enum_default(prop, CD_PROP_FLOAT);
RNA_def_property_ui_text(prop, "Data Type", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_geo_triangulate(StructRNA *srna)

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_devirtualize_parameters.hh"
#include "BLI_generic_array.hh"
#include "BLI_length_parameterize.hh"
#include "BKE_curves.hh"
@ -8,6 +9,8 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_sample_cc {
@ -16,9 +19,16 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveSample)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Curve"))
b.add_input<decl::Geometry>(N_("Curves"))
.only_realized_data()
.supported_type(GEO_COMPONENT_TYPE_CURVE);
b.add_input<decl::Float>(N_("Value"), "Value_Float").hide_value().supports_field();
b.add_input<decl::Int>(N_("Value"), "Value_Int").hide_value().supports_field();
b.add_input<decl::Vector>(N_("Value"), "Value_Vector").hide_value().supports_field();
b.add_input<decl::Color>(N_("Value"), "Value_Color").hide_value().supports_field();
b.add_input<decl::Bool>(N_("Value"), "Value_Bool").hide_value().supports_field();
b.add_input<decl::Float>(N_("Factor"))
.min(0.0f)
.max(1.0f)
@ -30,6 +40,16 @@ static void node_declare(NodeDeclarationBuilder &b)
.subtype(PROP_DISTANCE)
.supports_field()
.make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; });
b.add_input<decl::Int>(N_("Curve Index")).supports_field().make_available([](bNode &node) {
node_storage(node).use_all_curves = false;
});
b.add_output<decl::Float>(N_("Value"), "Value_Float").dependent_field();
b.add_output<decl::Int>(N_("Value"), "Value_Int").dependent_field();
b.add_output<decl::Vector>(N_("Value"), "Value_Vector").dependent_field();
b.add_output<decl::Color>(N_("Value"), "Value_Color").dependent_field();
b.add_output<decl::Bool>(N_("Value"), "Value_Bool").dependent_field();
b.add_output<decl::Vector>(N_("Position")).dependent_field();
b.add_output<decl::Vector>(N_("Tangent")).dependent_field();
b.add_output<decl::Vector>(N_("Normal")).dependent_field();
@ -37,13 +57,17 @@ static void node_declare(NodeDeclarationBuilder &b)
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE);
uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
uiItemR(layout, ptr, "use_all_curves", 0, nullptr, ICON_NONE);
}
static void node_type_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryCurveSample *data = MEM_cnew<NodeGeometryCurveSample>(__func__);
data->mode = GEO_NODE_CURVE_SAMPLE_LENGTH;
data->mode = GEO_NODE_CURVE_SAMPLE_FACTOR;
data->use_all_curves = false;
data->data_type = CD_PROP_FLOAT;
node->storage = data;
}
@ -51,16 +75,62 @@ static void node_update(bNodeTree *ntree, bNode *node)
{
const NodeGeometryCurveSample &storage = node_storage(*node);
const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode;
const eCustomDataType data_type = eCustomDataType(storage.data_type);
bNodeSocket *factor = static_cast<bNodeSocket *>(node->inputs.first)->next;
bNodeSocket *in_socket_float = static_cast<bNodeSocket *>(node->inputs.first)->next;
bNodeSocket *in_socket_int32 = in_socket_float->next;
bNodeSocket *in_socket_vector = in_socket_int32->next;
bNodeSocket *in_socket_color4f = in_socket_vector->next;
bNodeSocket *in_socket_bool = in_socket_color4f->next;
bNodeSocket *factor = in_socket_bool->next;
bNodeSocket *length = factor->next;
bNodeSocket *curve_index = length->next;
nodeSetSocketAvailability(ntree, factor, mode == GEO_NODE_CURVE_SAMPLE_FACTOR);
nodeSetSocketAvailability(ntree, length, mode == GEO_NODE_CURVE_SAMPLE_LENGTH);
nodeSetSocketAvailability(ntree, curve_index, !storage.use_all_curves);
nodeSetSocketAvailability(ntree, in_socket_vector, data_type == CD_PROP_FLOAT3);
nodeSetSocketAvailability(ntree, in_socket_float, data_type == CD_PROP_FLOAT);
nodeSetSocketAvailability(ntree, in_socket_color4f, data_type == CD_PROP_COLOR);
nodeSetSocketAvailability(ntree, in_socket_bool, data_type == CD_PROP_BOOL);
nodeSetSocketAvailability(ntree, in_socket_int32, data_type == CD_PROP_INT32);
bNodeSocket *out_socket_float = static_cast<bNodeSocket *>(node->outputs.first);
bNodeSocket *out_socket_int32 = out_socket_float->next;
bNodeSocket *out_socket_vector = out_socket_int32->next;
bNodeSocket *out_socket_color4f = out_socket_vector->next;
bNodeSocket *out_socket_bool = out_socket_color4f->next;
nodeSetSocketAvailability(ntree, out_socket_vector, data_type == CD_PROP_FLOAT3);
nodeSetSocketAvailability(ntree, out_socket_float, data_type == CD_PROP_FLOAT);
nodeSetSocketAvailability(ntree, out_socket_color4f, data_type == CD_PROP_COLOR);
nodeSetSocketAvailability(ntree, out_socket_bool, data_type == CD_PROP_BOOL);
nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().fixed_declaration;
search_link_ops_for_declarations(params, declaration.inputs().take_front(4));
search_link_ops_for_declarations(params, declaration.outputs().take_front(3));
const std::optional<eCustomDataType> type = node_data_type_to_custom_data_type(
eNodeSocketDatatype(params.other_socket().type));
if (type && *type != CD_PROP_STRING) {
/* The input and output sockets have the same name. */
params.add_item(IFACE_("Value"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSampleCurve");
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Value");
});
}
}
static void sample_indices_and_lengths(const Span<float> accumulated_lengths,
const Span<float> sample_lengths,
const GeometryNodeCurveSampleMode length_mode,
const IndexMask mask,
MutableSpan<int> r_segment_indices,
MutableSpan<float> r_length_in_segment)
@ -70,10 +140,13 @@ static void sample_indices_and_lengths(const Span<float> accumulated_lengths,
mask.to_best_mask_type([&](const auto mask) {
for (const int64_t i : mask) {
const float sample_length = length_mode == GEO_NODE_CURVE_SAMPLE_FACTOR ?
sample_lengths[i] * total_length :
sample_lengths[i];
int segment_i;
float factor_in_segment;
length_parameterize::sample_at_length(accumulated_lengths,
std::clamp(sample_lengths[i], 0.0f, total_length),
std::clamp(sample_length, 0.0f, total_length),
segment_i,
factor_in_segment,
&hint);
@ -89,6 +162,7 @@ static void sample_indices_and_lengths(const Span<float> accumulated_lengths,
static void sample_indices_and_factors_to_compressed(const Span<float> accumulated_lengths,
const Span<float> sample_lengths,
const GeometryNodeCurveSampleMode length_mode,
const IndexMask mask,
MutableSpan<int> r_segment_indices,
MutableSpan<float> r_factor_in_segment)
@ -96,16 +170,32 @@ static void sample_indices_and_factors_to_compressed(const Span<float> accumulat
const float total_length = accumulated_lengths.last();
length_parameterize::SampleSegmentHint hint;
mask.to_best_mask_type([&](const auto mask) {
for (const int64_t i : IndexRange(mask.size())) {
const float length = sample_lengths[mask[i]];
length_parameterize::sample_at_length(accumulated_lengths,
std::clamp(length, 0.0f, total_length),
r_segment_indices[i],
r_factor_in_segment[i],
&hint);
}
});
switch (length_mode) {
case GEO_NODE_CURVE_SAMPLE_FACTOR:
mask.to_best_mask_type([&](const auto mask) {
for (const int64_t i : IndexRange(mask.size())) {
const float length = sample_lengths[mask[i]] * total_length;
length_parameterize::sample_at_length(accumulated_lengths,
std::clamp(length, 0.0f, total_length),
r_segment_indices[i],
r_factor_in_segment[i],
&hint);
}
});
break;
case GEO_NODE_CURVE_SAMPLE_LENGTH:
mask.to_best_mask_type([&](const auto mask) {
for (const int64_t i : IndexRange(mask.size())) {
const float length = sample_lengths[mask[i]];
length_parameterize::sample_at_length(accumulated_lengths,
std::clamp(length, 0.0f, total_length),
r_segment_indices[i],
r_factor_in_segment[i],
&hint);
}
});
break;
}
}
/**
@ -115,10 +205,12 @@ static void sample_indices_and_factors_to_compressed(const Span<float> accumulat
class SampleFloatSegmentsFunction : public fn::MultiFunction {
private:
Array<float> accumulated_lengths_;
GeometryNodeCurveSampleMode length_mode_;
public:
SampleFloatSegmentsFunction(Array<float> accumulated_lengths)
: accumulated_lengths_(std::move(accumulated_lengths))
SampleFloatSegmentsFunction(Array<float> accumulated_lengths,
const GeometryNodeCurveSampleMode length_mode)
: accumulated_lengths_(std::move(accumulated_lengths)), length_mode_(length_mode)
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
@ -141,7 +233,8 @@ class SampleFloatSegmentsFunction : public fn::MultiFunction {
MutableSpan<float> lengths_in_segments = params.uninitialized_single_output<float>(
2, "Length in Curve");
sample_indices_and_lengths(accumulated_lengths_, lengths, mask, indices, lengths_in_segments);
sample_indices_and_lengths(
accumulated_lengths_, lengths, length_mode_, mask, indices, lengths_in_segments);
}
};
@ -153,15 +246,27 @@ class SampleCurveFunction : public fn::MultiFunction {
* that the curve is not freed before the function can execute.
*/
GeometrySet geometry_set_;
GField src_field_;
GeometryNodeCurveSampleMode length_mode_;
fn::MFSignature signature_;
std::optional<bke::CurvesFieldContext> source_context_;
std::unique_ptr<FieldEvaluator> source_evaluator_;
const GVArray *source_data_;
public:
SampleCurveFunction(GeometrySet geometry_set) : geometry_set_(std::move(geometry_set))
SampleCurveFunction(GeometrySet geometry_set,
const GeometryNodeCurveSampleMode length_mode,
const GField &src_field)
: geometry_set_(std::move(geometry_set)), src_field_(src_field), length_mode_(length_mode)
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
signature_ = create_signature();
this->set_signature(&signature_);
this->evaluate_source();
}
static fn::MFSignature create_signature()
fn::MFSignature create_signature()
{
blender::fn::MFSignatureBuilder signature{"Sample Curve"};
signature.single_input<int>("Curve Index");
@ -169,6 +274,7 @@ class SampleCurveFunction : public fn::MultiFunction {
signature.single_output<float3>("Position");
signature.single_output<float3>("Tangent");
signature.single_output<float3>("Normal");
signature.single_output("Value", src_field_.cpp_type());
return signature.build();
}
@ -180,6 +286,7 @@ class SampleCurveFunction : public fn::MultiFunction {
3, "Tangent");
MutableSpan<float3> sampled_normals = params.uninitialized_single_output_if_required<float3>(
4, "Normal");
GMutableSpan sampled_values = params.uninitialized_single_output_if_required(5, "Value");
auto return_default = [&]() {
if (!sampled_positions.is_empty()) {
@ -218,18 +325,40 @@ class SampleCurveFunction : public fn::MultiFunction {
Array<int> indices;
Array<float> factors;
GArray<> src_original_values(source_data_->type());
GArray<> src_evaluated_values(source_data_->type());
auto fill_invalid = [&](const IndexMask mask) {
if (!sampled_positions.is_empty()) {
sampled_positions.fill_indices(mask, float3(0));
}
if (!sampled_tangents.is_empty()) {
sampled_tangents.fill_indices(mask, float3(0));
}
if (!sampled_normals.is_empty()) {
sampled_normals.fill_indices(mask, float3(0));
}
if (!sampled_values.is_empty()) {
attribute_math::convert_to_static_type(source_data_->type(), [&](auto dummy) {
using T = decltype(dummy);
sampled_values.typed<T>().fill_indices(mask, {});
});
}
};
auto sample_curve = [&](const int curve_i, const IndexMask mask) {
const Span<float> accumulated_lengths = curves.evaluated_lengths_for_curve(curve_i,
cyclic[curve_i]);
if (accumulated_lengths.is_empty()) {
fill_invalid(mask);
return;
}
/* Store the sampled indices and factors in arrays the size of the mask.
* Then, during interpolation, move the results back to the masked indices. */
indices.reinitialize(mask.size());
factors.reinitialize(mask.size());
sample_indices_and_factors_to_compressed(
curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]),
lengths,
mask,
indices,
factors);
accumulated_lengths, lengths, length_mode_, mask, indices, factors);
const IndexRange evaluated_points = curves.evaluated_points_for_curve(curve_i);
if (!sampled_positions.is_empty()) {
@ -254,54 +383,67 @@ class SampleCurveFunction : public fn::MultiFunction {
sampled_normals[i] = math::normalize(sampled_normals[i]);
}
}
if (!sampled_values.is_empty()) {
const IndexRange points = curves.points_for_curve(curve_i);
src_original_values.reinitialize(points.size());
source_data_->materialize_compressed_to_uninitialized(points, src_original_values.data());
src_evaluated_values.reinitialize(curves.evaluated_points_for_curve(curve_i).size());
curves.interpolate_to_evaluated(curve_i, src_original_values, src_evaluated_values);
attribute_math::convert_to_static_type(source_data_->type(), [&](auto dummy) {
using T = decltype(dummy);
const Span<T> src_evaluated_values_typed = src_evaluated_values.as_span().typed<T>();
MutableSpan<T> sampled_values_typed = sampled_values.typed<T>();
length_parameterize::interpolate_to_masked<T>(
src_evaluated_values_typed, indices, factors, mask, sampled_values_typed);
});
}
};
if (curve_indices.is_single()) {
sample_curve(curve_indices.get_internal_single(), mask);
if (const std::optional<int> curve_i = curve_indices.get_if_single()) {
if (curves.curves_range().contains(*curve_i)) {
sample_curve(*curve_i, mask);
}
else {
fill_invalid(mask);
}
}
else {
Vector<int64_t> invalid_indices;
MultiValueMap<int, int64_t> indices_per_curve;
devirtualize_varray(curve_indices, [&](const auto curve_indices) {
for (const int64_t i : mask) {
indices_per_curve.add(curve_indices[i], i);
const int curve_i = curve_indices[i];
if (curves.curves_range().contains(curve_i)) {
indices_per_curve.add(curve_i, i);
}
else {
invalid_indices.append(i);
}
}
});
for (const int curve_i : indices_per_curve.keys()) {
sample_curve(curve_i, IndexMask(indices_per_curve.lookup(curve_i)));
}
fill_invalid(IndexMask(invalid_indices));
}
}
private:
void evaluate_source()
{
const Curves &curves_id = *geometry_set_.get_curves_for_read();
const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
source_context_.emplace(bke::CurvesFieldContext{curves, ATTR_DOMAIN_POINT});
source_evaluator_ = std::make_unique<FieldEvaluator>(*source_context_, curves.points_num());
source_evaluator_->add(src_field_);
source_evaluator_->evaluate();
source_data_ = &source_evaluator_->get_evaluated(0);
}
};
/**
* Pre-process the lengths or factors used for the sampling, turning factors into lengths, and
* clamping between zero and the total length of the curves. Do this as a separate operation in the
* field tree to make the sampling simpler, and to let the evaluator optimize better.
*
* \todo Use a mutable single input instead when they are supported.
*/
static Field<float> get_length_input_field(GeoNodeExecParams params,
const GeometryNodeCurveSampleMode mode,
const float curves_total_length)
{
if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) {
return params.extract_input<Field<float>>("Length");
}
/* Convert the factor to a length. */
Field<float> factor_field = params.get_input<Field<float>>("Factor");
auto clamp_fn = std::make_unique<fn::CustomMF_SI_SO<float, float>>(
__func__,
[curves_total_length](float factor) { return factor * curves_total_length; },
fn::CustomMF_presets::AllSpanOrSingle());
return Field<float>(FieldOperation::Create(std::move(clamp_fn), {std::move(factor_field)}), 0);
}
static Array<float> curve_accumulated_lengths(const bke::CurvesGeometry &curves)
{
curves.ensure_evaluated_lengths();
Array<float> curve_lengths(curves.curves_num());
const VArray<bool> cyclic = curves.cyclic();
@ -313,9 +455,56 @@ static Array<float> curve_accumulated_lengths(const bke::CurvesGeometry &curves)
return curve_lengths;
}
static GField get_input_attribute_field(GeoNodeExecParams &params, const eCustomDataType data_type)
{
switch (data_type) {
case CD_PROP_FLOAT:
return params.extract_input<Field<float>>("Value_Float");
case CD_PROP_FLOAT3:
return params.extract_input<Field<float3>>("Value_Vector");
case CD_PROP_COLOR:
return params.extract_input<Field<ColorGeometry4f>>("Value_Color");
case CD_PROP_BOOL:
return params.extract_input<Field<bool>>("Value_Bool");
case CD_PROP_INT32:
return params.extract_input<Field<int>>("Value_Int");
default:
BLI_assert_unreachable();
}
return {};
}
static void output_attribute_field(GeoNodeExecParams &params, GField field)
{
switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) {
case CD_PROP_FLOAT: {
params.set_output("Value_Float", Field<float>(field));
break;
}
case CD_PROP_FLOAT3: {
params.set_output("Value_Vector", Field<float3>(field));
break;
}
case CD_PROP_COLOR: {
params.set_output("Value_Color", Field<ColorGeometry4f>(field));
break;
}
case CD_PROP_BOOL: {
params.set_output("Value_Bool", Field<bool>(field));
break;
}
case CD_PROP_INT32: {
params.set_output("Value_Int", Field<int>(field));
break;
}
default:
break;
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curves");
if (!geometry_set.has_curves()) {
params.set_default_remaining_outputs();
return;
@ -328,18 +517,18 @@ static void node_geo_exec(GeoNodeExecParams params)
return;
}
Array<float> curve_lengths = curve_accumulated_lengths(curves);
const float total_length = curve_lengths.last();
if (total_length == 0.0f) {
params.set_default_remaining_outputs();
return;
}
curves.ensure_evaluated_lengths();
const NodeGeometryCurveSample &storage = node_storage(params.node());
const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode;
Field<float> length_field = get_length_input_field(params, mode, total_length);
const eCustomDataType data_type = eCustomDataType(storage.data_type);
auto sample_fn = std::make_unique<SampleCurveFunction>(std::move(geometry_set));
Field<float> length_field = params.extract_input<Field<float>>(
mode == GEO_NODE_CURVE_SAMPLE_FACTOR ? "Factor" : "Length");
GField src_values_field = get_input_attribute_field(params, data_type);
auto sample_fn = std::make_unique<SampleCurveFunction>(
std::move(geometry_set), mode, std::move(src_values_field));
std::shared_ptr<FieldOperation> sample_op;
if (curves.curves_num() == 1) {
@ -347,15 +536,26 @@ static void node_geo_exec(GeoNodeExecParams params)
{fn::make_constant_field<int>(0), std::move(length_field)});
}
else {
auto index_fn = std::make_unique<SampleFloatSegmentsFunction>(std::move(curve_lengths));
auto index_op = FieldOperation::Create(std::move(index_fn), {std::move(length_field)});
sample_op = FieldOperation::Create(std::move(sample_fn),
{Field<int>(index_op, 0), Field<float>(index_op, 1)});
Field<int> curve_index;
Field<float> length_in_curve;
if (storage.use_all_curves) {
auto index_fn = std::make_unique<SampleFloatSegmentsFunction>(
curve_accumulated_lengths(curves), mode);
auto index_op = FieldOperation::Create(std::move(index_fn), {std::move(length_field)});
curve_index = Field<int>(index_op, 0);
length_in_curve = Field<float>(index_op, 1);
}
else {
curve_index = params.extract_input<Field<int>>("Curve Index");
length_in_curve = std::move(length_field);
}
sample_op = FieldOperation::Create(std::move(sample_fn), {curve_index, length_in_curve});
}
params.set_output("Position", Field<float3>(sample_op, 0));
params.set_output("Tangent", Field<float3>(sample_op, 1));
params.set_output("Normal", Field<float3>(sample_op, 2));
output_attribute_field(params, GField(sample_op, 3));
}
} // namespace blender::nodes::node_geo_curve_sample_cc
@ -374,6 +574,6 @@ void register_node_type_geo_curve_sample()
node_type_storage(
&ntype, "NodeGeometryCurveSample", node_free_standard_storage, node_copy_standard_storage);
ntype.draw_buttons = file_ns::node_layout;
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
nodeRegisterType(&ntype);
}