Blender V4.3
node_geo_curve_sample.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_math_color.hh"
7#include "BLI_math_vector.hh"
8
11
12#include "BKE_curves.hh"
13
14#include "UI_interface.hh"
15#include "UI_resources.hh"
16
18
19#include "node_geometry_util.hh"
20
22
24
26{
27 b.add_input<decl::Geometry>("Curves").only_realized_data().supported_type(
29
30 if (const bNode *node = b.node_or_null()) {
31 const NodeGeometryCurveSample &storage = node_storage(*node);
32 b.add_input(eCustomDataType(storage.data_type), "Value").hide_value().field_on_all();
33 }
34
35 auto &factor = b.add_input<decl::Float>("Factor")
36 .min(0.0f)
37 .max(1.0f)
39 .field_on_all()
40 .make_available([](bNode &node) {
41 node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR;
42 });
43 auto &length = b.add_input<decl::Float>("Length")
44 .min(0.0f)
46 .field_on_all()
47 .make_available([](bNode &node) {
48 node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH;
49 });
50 auto &index =
51 b.add_input<decl::Int>("Curve Index").field_on_all().make_available([](bNode &node) {
52 node_storage(node).use_all_curves = false;
53 });
54
55 if (const bNode *node = b.node_or_null()) {
56 const NodeGeometryCurveSample &storage = node_storage(*node);
58 b.add_output(eCustomDataType(storage.data_type), "Value").dependent_field({2, 3, 4});
59
60 factor.available(mode == GEO_NODE_CURVE_SAMPLE_FACTOR);
61 length.available(mode == GEO_NODE_CURVE_SAMPLE_LENGTH);
62 index.available(!storage.use_all_curves);
63 }
64
65 b.add_output<decl::Vector>("Position").dependent_field({2, 3, 4});
66 b.add_output<decl::Vector>("Tangent").dependent_field({2, 3, 4});
67 b.add_output<decl::Vector>("Normal").dependent_field({2, 3, 4});
68}
69
70static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
71{
72 uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
73 uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
74 uiItemR(layout, ptr, "use_all_curves", UI_ITEM_NONE, nullptr, ICON_NONE);
75}
76
77static void node_init(bNodeTree * /*tree*/, bNode *node)
78{
79 NodeGeometryCurveSample *data = MEM_cnew<NodeGeometryCurveSample>(__func__);
81 data->use_all_curves = false;
82 data->data_type = CD_PROP_FLOAT;
83 node->storage = data;
84}
85
87{
88 const NodeDeclaration &declaration = *params.node_type().static_declaration;
89 search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1));
90 search_link_ops_for_declarations(params, declaration.inputs.as_span().take_back(3));
91 search_link_ops_for_declarations(params, declaration.outputs.as_span().take_back(3));
92
93 const std::optional<eCustomDataType> type = bke::socket_type_to_custom_data_type(
94 eNodeSocketDatatype(params.other_socket().type));
95 if (type && *type != CD_PROP_STRING) {
96 /* The input and output sockets have the same name. */
97 params.add_item(IFACE_("Value"), [type](LinkSearchOpParams &params) {
98 bNode &node = params.add_node("GeometryNodeSampleCurve");
99 node_storage(node).data_type = *type;
100 params.update_and_connect_available_socket(node, "Value");
101 });
102 }
103}
104
105static void sample_indices_and_lengths(const Span<float> accumulated_lengths,
106 const Span<float> sample_lengths,
107 const GeometryNodeCurveSampleMode length_mode,
108 const IndexMask &mask,
109 MutableSpan<int> r_segment_indices,
110 MutableSpan<float> r_length_in_segment)
111{
112 const float total_length = accumulated_lengths.last();
114
115 mask.foreach_index_optimized<int>([&](const int i) {
116 const float sample_length = length_mode == GEO_NODE_CURVE_SAMPLE_FACTOR ?
117 sample_lengths[i] * total_length :
118 sample_lengths[i];
119 int segment_i;
120 float factor_in_segment;
121 length_parameterize::sample_at_length(accumulated_lengths,
122 std::clamp(sample_length, 0.0f, total_length),
123 segment_i,
124 factor_in_segment,
125 &hint);
126 const float segment_start = segment_i == 0 ? 0.0f : accumulated_lengths[segment_i - 1];
127 const float segment_end = accumulated_lengths[segment_i];
128 const float segment_length = segment_end - segment_start;
129
130 r_segment_indices[i] = segment_i;
131 r_length_in_segment[i] = factor_in_segment * segment_length;
132 });
133}
134
135static void sample_indices_and_factors_to_compressed(const Span<float> accumulated_lengths,
136 const Span<float> sample_lengths,
137 const GeometryNodeCurveSampleMode length_mode,
138 const IndexMask &mask,
139 MutableSpan<int> r_segment_indices,
140 MutableSpan<float> r_factor_in_segment)
141{
142 const float total_length = accumulated_lengths.last();
144
145 switch (length_mode) {
147 mask.foreach_index_optimized<int>([&](const int i, const int pos) {
148 const float length = sample_lengths[i] * total_length;
149 length_parameterize::sample_at_length(accumulated_lengths,
150 std::clamp(length, 0.0f, total_length),
151 r_segment_indices[pos],
152 r_factor_in_segment[pos],
153 &hint);
154 });
155 break;
157 mask.foreach_index_optimized<int>([&](const int i, const int pos) {
158 const float length = sample_lengths[i];
159 length_parameterize::sample_at_length(accumulated_lengths,
160 std::clamp(length, 0.0f, total_length),
161 r_segment_indices[pos],
162 r_factor_in_segment[pos],
163 &hint);
164 });
165 break;
166 }
167}
168
174 private:
175 Array<float> accumulated_lengths_;
176 GeometryNodeCurveSampleMode length_mode_;
177
178 public:
180 const GeometryNodeCurveSampleMode length_mode)
181 : accumulated_lengths_(std::move(accumulated_lengths)), length_mode_(length_mode)
182 {
183 static const mf::Signature signature = []() {
185 mf::SignatureBuilder builder{"Sample Curve Index", signature};
186 builder.single_input<float>("Length");
187
188 builder.single_output<int>("Curve Index");
189 builder.single_output<float>("Length in Curve");
190 return signature;
191 }();
192 this->set_signature(&signature);
193 }
194
195 void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
196 {
197 const VArraySpan<float> lengths = params.readonly_single_input<float>(0, "Length");
198 MutableSpan<int> indices = params.uninitialized_single_output<int>(1, "Curve Index");
199 MutableSpan<float> lengths_in_segments = params.uninitialized_single_output<float>(
200 2, "Length in Curve");
201
203 accumulated_lengths_, lengths, length_mode_, mask, indices, lengths_in_segments);
204 }
205};
206
208 private:
214 GeometrySet geometry_set_;
215 GField src_field_;
216 GeometryNodeCurveSampleMode length_mode_;
217
218 mf::Signature signature_;
219
220 std::optional<bke::CurvesFieldContext> source_context_;
221 std::unique_ptr<FieldEvaluator> source_evaluator_;
222 const GVArray *source_data_;
223
224 public:
226 const GeometryNodeCurveSampleMode length_mode,
227 const GField &src_field)
228 : geometry_set_(std::move(geometry_set)), src_field_(src_field), length_mode_(length_mode)
229 {
230 mf::SignatureBuilder builder{"Sample Curve", signature_};
231 builder.single_input<int>("Curve Index");
232 builder.single_input<float>("Length");
233 builder.single_output<float3>("Position", mf::ParamFlag::SupportsUnusedOutput);
234 builder.single_output<float3>("Tangent", mf::ParamFlag::SupportsUnusedOutput);
235 builder.single_output<float3>("Normal", mf::ParamFlag::SupportsUnusedOutput);
236 builder.single_output("Value", src_field_.cpp_type(), mf::ParamFlag::SupportsUnusedOutput);
237 this->set_signature(&signature_);
238
239 this->evaluate_source();
240 }
241
242 void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
243 {
244 MutableSpan<float3> sampled_positions = params.uninitialized_single_output_if_required<float3>(
245 2, "Position");
246 MutableSpan<float3> sampled_tangents = params.uninitialized_single_output_if_required<float3>(
247 3, "Tangent");
248 MutableSpan<float3> sampled_normals = params.uninitialized_single_output_if_required<float3>(
249 4, "Normal");
250 GMutableSpan sampled_values = params.uninitialized_single_output_if_required(5, "Value");
251
252 auto return_default = [&]() {
253 if (!sampled_positions.is_empty()) {
254 index_mask::masked_fill(sampled_positions, {0, 0, 0}, mask);
255 }
256 if (!sampled_tangents.is_empty()) {
257 index_mask::masked_fill(sampled_tangents, {0, 0, 0}, mask);
258 }
259 if (!sampled_normals.is_empty()) {
260 index_mask::masked_fill(sampled_normals, {0, 0, 0}, mask);
261 }
262 };
263
264 if (!geometry_set_.has_curves()) {
265 return return_default();
266 }
267
268 const Curves &curves_id = *geometry_set_.get_curves();
269 const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
270 if (curves.points_num() == 0) {
271 return return_default();
272 }
273 curves.ensure_can_interpolate_to_evaluated();
274 Span<float3> evaluated_positions = curves.evaluated_positions();
275 Span<float3> evaluated_tangents;
276 Span<float3> evaluated_normals;
277 if (!sampled_tangents.is_empty()) {
278 evaluated_tangents = curves.evaluated_tangents();
279 }
280 if (!sampled_normals.is_empty()) {
281 evaluated_normals = curves.evaluated_normals();
282 }
283
284 const OffsetIndices points_by_curve = curves.points_by_curve();
285 const OffsetIndices evaluated_points_by_curve = curves.evaluated_points_by_curve();
286 const VArray<int> curve_indices = params.readonly_single_input<int>(0, "Curve Index");
287 const VArraySpan<float> lengths = params.readonly_single_input<float>(1, "Length");
288 const VArray<bool> cyclic = curves.cyclic();
289
291 Array<float> factors;
292 GArray<> src_original_values(source_data_->type());
293 GArray<> src_evaluated_values(source_data_->type());
294
295 auto fill_invalid = [&](const IndexMask &mask) {
296 if (!sampled_positions.is_empty()) {
297 index_mask::masked_fill(sampled_positions, float3(0), mask);
298 }
299 if (!sampled_tangents.is_empty()) {
300 index_mask::masked_fill(sampled_tangents, float3(0), mask);
301 }
302 if (!sampled_normals.is_empty()) {
303 index_mask::masked_fill(sampled_normals, float3(0), mask);
304 }
305 if (!sampled_values.is_empty()) {
306 bke::attribute_math::convert_to_static_type(source_data_->type(), [&](auto dummy) {
307 using T = decltype(dummy);
308 index_mask::masked_fill<T>(sampled_values.typed<T>(), {}, mask);
309 });
310 }
311 };
312
313 auto sample_curve = [&](const int curve_i, const IndexMask &mask) {
314 const Span<float> accumulated_lengths = curves.evaluated_lengths_for_curve(curve_i,
315 cyclic[curve_i]);
316 if (accumulated_lengths.is_empty()) {
317 fill_invalid(mask);
318 return;
319 }
320 /* Store the sampled indices and factors in arrays the size of the mask.
321 * Then, during interpolation, move the results back to the masked indices. */
322 indices.reinitialize(mask.size());
323 factors.reinitialize(mask.size());
325 accumulated_lengths, lengths, length_mode_, mask, indices, factors);
326
327 const IndexRange evaluated_points = evaluated_points_by_curve[curve_i];
328 if (!sampled_positions.is_empty()) {
330 evaluated_positions.slice(evaluated_points),
331 indices,
332 factors,
333 mask,
334 sampled_positions);
335 }
336 if (!sampled_tangents.is_empty()) {
338 evaluated_tangents.slice(evaluated_points), indices, factors, mask, sampled_tangents);
339 mask.foreach_index(
340 [&](const int i) { sampled_tangents[i] = math::normalize(sampled_tangents[i]); });
341 }
342 if (!sampled_normals.is_empty()) {
344 evaluated_normals.slice(evaluated_points), indices, factors, mask, sampled_normals);
345 mask.foreach_index(
346 [&](const int i) { sampled_normals[i] = math::normalize(sampled_normals[i]); });
347 }
348 if (!sampled_values.is_empty()) {
349 const IndexRange points = points_by_curve[curve_i];
350 src_original_values.reinitialize(points.size());
351 source_data_->materialize_compressed_to_uninitialized(points, src_original_values.data());
352 src_evaluated_values.reinitialize(evaluated_points.size());
353 curves.interpolate_to_evaluated(curve_i, src_original_values, src_evaluated_values);
354 bke::attribute_math::convert_to_static_type(source_data_->type(), [&](auto dummy) {
355 using T = decltype(dummy);
356 const Span<T> src_evaluated_values_typed = src_evaluated_values.as_span().typed<T>();
357 MutableSpan<T> sampled_values_typed = sampled_values.typed<T>();
358 length_parameterize::interpolate_to_masked<T>(
359 src_evaluated_values_typed, indices, factors, mask, sampled_values_typed);
360 });
361 }
362 };
363
364 if (const std::optional<int> curve_i = curve_indices.get_if_single()) {
365 if (curves.curves_range().contains(*curve_i)) {
366 sample_curve(*curve_i, mask);
367 }
368 else {
369 fill_invalid(mask);
370 }
371 }
372 else {
373 Vector<int> valid_indices;
374 Vector<int> invalid_indices;
375 VectorSet<int> used_curves;
376 devirtualize_varray(curve_indices, [&](const auto curve_indices) {
377 mask.foreach_index([&](const int i) {
378 const int curve_i = curve_indices[i];
379 if (curves.curves_range().contains(curve_i)) {
380 used_curves.add(curve_i);
381 valid_indices.append(i);
382 }
383 else {
384 invalid_indices.append(i);
385 }
386 });
387 });
388
389 IndexMaskMemory memory;
390 const IndexMask valid_indices_mask = valid_indices.size() == mask.size() ?
391 mask :
392 IndexMask::from_indices(valid_indices.as_span(),
393 memory);
394 Array<IndexMask> mask_by_curve(used_curves.size());
396 valid_indices_mask,
397 memory,
398 [&](const int i) { return used_curves.index_of(curve_indices[i]); },
399 mask_by_curve);
400
401 for (const int i : mask_by_curve.index_range()) {
402 sample_curve(used_curves[i], mask_by_curve[i]);
403 }
404 fill_invalid(IndexMask::from_indices<int>(invalid_indices, memory));
405 }
406 }
407
408 private:
409 void evaluate_source()
410 {
411 const Curves &curves_id = *geometry_set_.get_curves();
412 const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
413 source_context_.emplace(bke::CurvesFieldContext{curves_id, AttrDomain::Point});
414 source_evaluator_ = std::make_unique<FieldEvaluator>(*source_context_, curves.points_num());
415 source_evaluator_->add(src_field_);
416 source_evaluator_->evaluate();
417 source_data_ = &source_evaluator_->get_evaluated(0);
418 }
419};
420
422{
423
424 Array<float> curve_lengths(curves.curves_num());
425 const VArray<bool> cyclic = curves.cyclic();
426 float length = 0.0f;
427 for (const int i : curves.curves_range()) {
428 length += curves.evaluated_length_total_for_curve(i, cyclic[i]);
429 curve_lengths[i] = length;
430 }
431 return curve_lengths;
432}
433
435{
436 GeometrySet geometry_set = params.extract_input<GeometrySet>("Curves");
437 if (!geometry_set.has_curves()) {
438 params.set_default_remaining_outputs();
439 return;
440 }
441
442 const Curves &curves_id = *geometry_set.get_curves();
443 const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
444 if (curves.points_num() == 0) {
445 params.set_default_remaining_outputs();
446 return;
447 }
448
449 curves.ensure_evaluated_lengths();
450
451 const NodeGeometryCurveSample &storage = node_storage(params.node());
453
454 Field<float> length_field = params.extract_input<Field<float>>(
455 mode == GEO_NODE_CURVE_SAMPLE_FACTOR ? "Factor" : "Length");
456 GField src_values_field = params.extract_input<GField>("Value");
457
458 std::shared_ptr<FieldOperation> sample_op;
459 if (curves.curves_num() == 1) {
460 sample_op = FieldOperation::Create(
461 std::make_unique<SampleCurveFunction>(
462 std::move(geometry_set), mode, std::move(src_values_field)),
463 {fn::make_constant_field<int>(0), std::move(length_field)});
464 }
465 else {
466 if (storage.use_all_curves) {
467 auto index_fn = std::make_unique<SampleFloatSegmentsFunction>(
468 curve_accumulated_lengths(curves), mode);
469 auto index_op = FieldOperation::Create(std::move(index_fn), {std::move(length_field)});
470 Field<int> curve_index = Field<int>(index_op, 0);
471 Field<float> length_in_curve = Field<float>(index_op, 1);
472 sample_op = FieldOperation::Create(
473 std::make_unique<SampleCurveFunction>(
474 std::move(geometry_set), GEO_NODE_CURVE_SAMPLE_LENGTH, std::move(src_values_field)),
475 {std::move(curve_index), std::move(length_in_curve)});
476 }
477 else {
478 Field<int> curve_index = params.extract_input<Field<int>>("Curve Index");
479 Field<float> length_in_curve = std::move(length_field);
480 sample_op = FieldOperation::Create(
481 std::make_unique<SampleCurveFunction>(
482 std::move(geometry_set), mode, std::move(src_values_field)),
483 {std::move(curve_index), std::move(length_in_curve)});
484 }
485 }
486
487 params.set_output("Position", Field<float3>(sample_op, 0));
488 params.set_output("Tangent", Field<float3>(sample_op, 1));
489 params.set_output("Normal", Field<float3>(sample_op, 2));
490 params.set_output("Value", GField(sample_op, 3));
491}
492
493static void node_register()
494{
495 static blender::bke::bNodeType ntype;
496
497 geo_node_type_base(&ntype, GEO_NODE_SAMPLE_CURVE, "Sample Curve", NODE_CLASS_GEOMETRY);
499 ntype.declare = node_declare;
500 ntype.initfunc = node_init;
502 &ntype, "NodeGeometryCurveSample", node_free_standard_storage, node_copy_standard_storage);
506}
507NOD_REGISTER_NODE(node_register)
508
509} // namespace blender::nodes::node_geo_curve_sample_cc
Low-level operations for curves.
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:418
#define IFACE_(msgid)
@ CD_PROP_FLOAT
@ CD_PROP_STRING
GeometryNodeCurveSampleMode
@ GEO_NODE_CURVE_SAMPLE_FACTOR
@ GEO_NODE_CURVE_SAMPLE_LENGTH
eNodeSocketDatatype
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_DISTANCE
Definition RNA_types.hh:159
@ PROP_FACTOR
Definition RNA_types.hh:154
#define UI_ITEM_NONE
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
@ UI_ITEM_R_EXPAND
SIMD_FORCE_INLINE btScalar length() const
Return the length of the vector.
Definition btVector3.h:257
void materialize_compressed_to_uninitialized(const IndexMask &mask, void *dst) const
constexpr int64_t size() const
constexpr bool is_empty() const
Definition BLI_span.hh:510
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:326
constexpr bool is_empty() const
Definition BLI_span.hh:261
const CPPType & cpp_type() const
Definition FN_field.hh:132
void set_signature(const Signature *signature)
static IndexMask from_indices(Span< T > indices, IndexMaskMemory &memory)
static void from_groups(const IndexMask &universe, IndexMaskMemory &memory, Fn &&get_group_index, MutableSpan< IndexMask > r_masks)
Vector< SocketDeclaration * > inputs
Vector< SocketDeclaration * > outputs
void make_available(bNode &node) const
SampleCurveFunction(GeometrySet geometry_set, const GeometryNodeCurveSampleMode length_mode, const GField &src_field)
void call(const IndexMask &mask, mf::Params params, mf::Context) const override
void call(const IndexMask &mask, mf::Params params, mf::Context) const override
SampleFloatSegmentsFunction(Array< float > accumulated_lengths, const GeometryNodeCurveSampleMode length_mode)
local_group_size(16, 16) .push_constant(Type b
static ushort indices[]
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
ccl_device_inline float4 mask(const int4 mask, const float4 a)
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
std::optional< eCustomDataType > socket_type_to_custom_data_type(eNodeSocketDatatype type)
Definition node.cc:4379
void masked_fill(MutableSpan< T > data, const T &value, const IndexMask &mask)
void sample_at_length(const Span< float > accumulated_segment_lengths, const float sample_length, int &r_segment_index, float &r_factor, SampleSegmentHint *hint=nullptr)
void interpolate_to_masked(const Span< T > src, const Span< int > indices, const Span< float > factors, const IndexMask &dst_mask, MutableSpan< T > dst)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
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)
static Array< float > curve_accumulated_lengths(const bke::CurvesGeometry &curves)
static void node_declare(NodeDeclarationBuilder &b)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static void node_geo_exec(GeoNodeExecParams params)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
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)
static void node_init(bNodeTree *, bNode *node)
void search_link_ops_for_declarations(GatherLinkSearchOpParams &params, Span< SocketDeclaration * > declarations)
void devirtualize_varray(const VArray< T > &varray, const Func &func, bool enable=true)
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:46
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:58
#define min(a, b)
Definition sort.c:32
CurvesGeometry geometry
const Curves * get_curves() const
Defines a node type.
Definition BKE_node.hh:218
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:267
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:339
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition BKE_node.hh:363
NodeDeclareFunction declare
Definition BKE_node.hh:347
PointerRNA * ptr
Definition wm_files.cc:4126