Blender V5.0
node_geo_interpolate_curves.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
6
7#include "BLI_math_vector.hh"
8
9#include "BLI_kdtree.h"
12#include "BLI_math_rotation.h"
13#include "BLI_task.hh"
14
15#include "BKE_curves.hh"
16
18
19#include "GEO_randomize.hh"
20
22
24{
25 b.add_input<decl::Geometry>("Guide Curves")
26 .description("Base curves that new curves are interpolated between");
27 b.add_input<decl::Vector>("Guide Up")
28 .field_on({0})
29 .hide_value()
30 .description("Optional up vector that is typically a surface normal");
31 b.add_input<decl::Int>("Guide Group ID")
32 .field_on({0})
33 .hide_value()
35 "Splits guides into separate groups. New curves interpolate existing curves "
36 "from a single group");
37 b.add_input<decl::Geometry>("Points").description(
38 "First control point positions for new interpolated curves");
39 b.add_input<decl::Vector>("Point Up")
40 .field_on({3})
41 .hide_value()
42 .description("Optional up vector that is typically a surface normal");
43 b.add_input<decl::Int>("Point Group ID")
44 .field_on({3})
45 .hide_value()
46 .description("The curve group to interpolate in");
47 b.add_input<decl::Int>("Max Neighbors")
48 .default_value(4)
49 .min(1)
51 "Maximum amount of close guide curves that are taken into account for interpolation");
52 b.add_output<decl::Geometry>("Curves").propagate_all();
53 b.add_output<decl::Int>("Closest Index")
54 .field_on_all()
55 .description("Index of the closest guide curve for each generated curve");
56 b.add_output<decl::Float>("Closest Weight")
57 .field_on_all()
58 .description("Weight of the closest guide curve for each generated curve");
59}
60
66{
67 MultiValueMap<int, int> guides_by_group;
68 for (const int curve_i : guide_group_ids.index_range()) {
69 const int group = guide_group_ids[curve_i];
70 guides_by_group.add(group, curve_i);
71 }
72 return guides_by_group;
73}
74
80 const MultiValueMap<int, int> &guides_by_group, const bke::CurvesGeometry &guide_curves)
81{
82 const OffsetIndices points_by_curve = guide_curves.points_by_curve();
83 Map<int, int> points_per_curve_by_group;
84 for (const auto &[group, guide_curve_indices] : guides_by_group.items()) {
85 int group_control_points = points_by_curve[guide_curve_indices[0]].size();
86 for (const int guide_curve_i : guide_curve_indices.as_span().drop_front(1)) {
87 const int control_points = points_by_curve[guide_curve_i].size();
88 if (group_control_points != control_points) {
89 group_control_points = -1;
90 break;
91 }
92 }
93 if (group_control_points != -1) {
94 points_per_curve_by_group.add(group, group_control_points);
95 }
96 }
97 return points_per_curve_by_group;
98}
99
104 const MultiValueMap<int, int> &guides_by_group, const bke::CurvesGeometry &guide_curves)
105{
106 Map<int, KDTree_3d *> kdtrees;
107 const Span<float3> positions = guide_curves.positions();
108 const Span<int> offsets = guide_curves.offsets();
109
110 for (const auto item : guides_by_group.items()) {
111 const int group = item.key;
112 const Span<int> guide_indices = item.value;
113
114 KDTree_3d *kdtree = BLI_kdtree_3d_new(guide_indices.size());
115 kdtrees.add_new(group, kdtree);
116
117 for (const int curve_i : guide_indices) {
118 const int first_point_i = offsets[curve_i];
119 const float3 &root_pos = positions[first_point_i];
120 BLI_kdtree_3d_insert(kdtree, curve_i, root_pos);
121 }
122 }
124 [](KDTree_3d *kdtree) { BLI_kdtree_3d_balance(kdtree); });
125 return kdtrees;
126}
127
132static void find_neighbor_guides(const Span<float3> positions,
133 const VArray<int> point_group_ids,
134 const Map<int, KDTree_3d *> kdtrees,
135 const MultiValueMap<int, int> &guides_by_group,
136 const int max_neighbor_count,
137 MutableSpan<int> r_all_neighbor_indices,
138 MutableSpan<float> r_all_neighbor_weights,
139 MutableSpan<int> r_all_neighbor_counts)
140{
141 threading::parallel_for(positions.index_range(), 128, [&](const IndexRange range) {
142 for (const int child_curve_i : range) {
143 const float3 &position = positions[child_curve_i];
144 const int group = point_group_ids[child_curve_i];
145 const KDTree_3d *kdtree = kdtrees.lookup_default(group, nullptr);
146 if (kdtree == nullptr) {
147 r_all_neighbor_counts[child_curve_i] = 0;
148 continue;
149 }
150
151 const int num_guides_in_group = guides_by_group.lookup(group).size();
152 /* Finding an additional neighbor that currently has weight zero is necessary to ensure that
153 * curves close by but with different guides still look similar. Otherwise there can be
154 * visible artifacts. */
155 const bool use_extra_neighbor = num_guides_in_group > max_neighbor_count;
156 const int neighbors_to_find = max_neighbor_count + use_extra_neighbor;
157
158 Vector<KDTreeNearest_3d, 16> nearest_n(neighbors_to_find);
159 const int num_neighbors = BLI_kdtree_3d_find_nearest_n(
160 kdtree, position, nearest_n.data(), neighbors_to_find);
161 if (num_neighbors == 0) {
162 r_all_neighbor_counts[child_curve_i] = 0;
163 continue;
164 }
165
166 const IndexRange neighbors_range{child_curve_i * max_neighbor_count, max_neighbor_count};
167 MutableSpan<int> neighbor_indices = r_all_neighbor_indices.slice(neighbors_range);
168 MutableSpan<float> neighbor_weights = r_all_neighbor_weights.slice(neighbors_range);
169
170 float tot_weight = 0.0f;
171 /* A different weighting algorithm is necessary for smooth transitions when desired. */
172 if (use_extra_neighbor) {
173 /* Find the distance to the guide with the largest distance. At this distance, the weight
174 * should become zero. */
175 const float max_distance = std::max_element(
176 nearest_n.begin(),
177 nearest_n.begin() + num_neighbors,
178 [](const KDTreeNearest_3d &a, const KDTreeNearest_3d &b) {
179 return a.dist < b.dist;
180 })
181 ->dist;
182 if (max_distance == 0.0f) {
183 r_all_neighbor_counts[child_curve_i] = 1;
184 neighbor_indices[0] = nearest_n[0].index;
185 neighbor_weights[0] = 1.0f;
186 continue;
187 }
188
189 int neighbor_counter = 0;
190 for (const int neighbor_i : IndexRange(num_neighbors)) {
191 const KDTreeNearest_3d &nearest = nearest_n[neighbor_i];
192 /* Goal for this weight calculation:
193 * - As distance gets closer to zero, it should become very large.
194 * - At `max_distance` the weight should be zero. */
195 const float weight = (max_distance - nearest.dist) / std::max(nearest.dist, 0.000001f);
196 if (weight > 0.0f) {
197 tot_weight += weight;
198 neighbor_indices[neighbor_counter] = nearest.index;
199 neighbor_weights[neighbor_counter] = weight;
200 neighbor_counter++;
201 }
202 }
203 r_all_neighbor_counts[child_curve_i] = neighbor_counter;
204 }
205 else {
206 int neighbor_counter = 0;
207 for (const int neighbor_i : IndexRange(num_neighbors)) {
208 const KDTreeNearest_3d &nearest = nearest_n[neighbor_i];
209 /* Goal for this weight calculation:
210 * - As the distance gets closer to zero, it should become very large.
211 * - As the distance gets larger, the weight should become zero. */
212 const float weight = 1.0f / std::max(nearest.dist, 0.000001f);
213 if (weight > 0.0f) {
214 tot_weight += weight;
215 neighbor_indices[neighbor_counter] = nearest.index;
216 neighbor_weights[neighbor_counter] = weight;
217 neighbor_counter++;
218 }
219 }
220 r_all_neighbor_counts[child_curve_i] = neighbor_counter;
221 }
222 if (tot_weight > 0.0f) {
223 /* Normalize weights so that their sum is 1. */
224 const float weight_factor = 1.0f / tot_weight;
225 for (float &weight : neighbor_weights.take_front(r_all_neighbor_counts[child_curve_i])) {
226 weight *= weight_factor;
227 }
228 }
229 }
230 });
231}
232
238 const VArray<int> &point_group_ids,
239 const Map<int, int> &points_per_curve_by_group,
240 const Span<int> all_neighbor_indices,
241 const Span<float> all_neighbor_weights,
242 const Span<int> all_neighbor_counts,
243 const int max_neighbors,
244 MutableSpan<int> r_points_per_child,
245 MutableSpan<bool> r_use_direct_interpolation)
246{
247 const OffsetIndices guide_points_by_curve = guide_curves.points_by_curve();
248 threading::parallel_for(r_points_per_child.index_range(), 512, [&](const IndexRange range) {
249 for (const int child_curve_i : range) {
250 const int neighbor_count = all_neighbor_counts[child_curve_i];
251 if (neighbor_count == 0) {
252 r_points_per_child[child_curve_i] = 1;
253 r_use_direct_interpolation[child_curve_i] = false;
254 continue;
255 }
256 const int group = point_group_ids[child_curve_i];
257 const int points_per_curve_in_group = points_per_curve_by_group.lookup_default(group, -1);
258 if (points_per_curve_in_group != -1) {
259 r_points_per_child[child_curve_i] = points_per_curve_in_group;
260 r_use_direct_interpolation[child_curve_i] = true;
261 continue;
262 }
263 const IndexRange neighbors_range{child_curve_i * max_neighbors, neighbor_count};
264 const Span<float> neighbor_weights = all_neighbor_weights.slice(neighbors_range);
265 const Span<int> neighbor_indices = all_neighbor_indices.slice(neighbors_range);
266
267 float neighbor_points_weighted_sum = 0.0f;
268 for (const int neighbor_i : IndexRange(neighbor_count)) {
269 const int neighbor_index = neighbor_indices[neighbor_i];
270 const float neighbor_weight = neighbor_weights[neighbor_i];
271 const int neighbor_points = guide_points_by_curve[neighbor_index].size();
272 neighbor_points_weighted_sum += neighbor_weight * float(neighbor_points);
273 }
274 const int points_in_child = std::max<int>(1, roundf(neighbor_points_weighted_sum));
275 r_points_per_child[child_curve_i] = points_in_child;
276 r_use_direct_interpolation[child_curve_i] = false;
277 }
278 });
279}
280
284static void parameterize_guide_curves(const bke::CurvesGeometry &guide_curves,
285 Array<int> &r_parameterized_guide_offsets,
286 Array<float> &r_parameterized_guide_lengths)
287{
288 r_parameterized_guide_offsets.reinitialize(guide_curves.curves_num() + 1);
289 const OffsetIndices guide_points_by_curve = guide_curves.points_by_curve();
290 threading::parallel_for(guide_curves.curves_range(), 1024, [&](const IndexRange range) {
291 for (const int guide_curve_i : range) {
292 r_parameterized_guide_offsets[guide_curve_i] = length_parameterize::segments_num(
293 guide_points_by_curve[guide_curve_i].size(), false);
294 }
295 });
296 offset_indices::accumulate_counts_to_offsets(r_parameterized_guide_offsets);
297 const OffsetIndices<int> parameterize_offsets{r_parameterized_guide_offsets};
298
299 r_parameterized_guide_lengths.reinitialize(r_parameterized_guide_offsets.last());
300 const Span<float3> guide_positions = guide_curves.positions();
301 threading::parallel_for(guide_curves.curves_range(), 256, [&](const IndexRange range) {
302 for (const int guide_curve_i : range) {
303 const IndexRange points = guide_points_by_curve[guide_curve_i];
304 const IndexRange lengths_range = parameterize_offsets[guide_curve_i];
305 length_parameterize::accumulate_lengths<float3>(
306 guide_positions.slice(points),
307 false,
308 r_parameterized_guide_lengths.as_mutable_span().slice(lengths_range));
309 }
310 });
311}
312
317 const bke::CurvesGeometry &guide_curves,
318 const int max_neighbors,
319 const Span<int> all_neighbor_indices,
320 const Span<float> all_neighbor_weights,
321 const Span<int> all_neighbor_counts,
322 const VArray<float3> &guides_up,
323 const VArray<float3> &points_up,
324 const Span<float3> point_positions,
325 const OffsetIndices<int> parameterized_guide_offsets,
326 const Span<float> parameterized_guide_lengths,
327 const Span<bool> use_direct_interpolation_per_child)
328{
329 const OffsetIndices guide_points_by_curve = guide_curves.points_by_curve();
330 const OffsetIndices child_points_by_curve = child_curves.points_by_curve();
331 const MutableSpan<float3> children_positions = child_curves.positions_for_write();
332 const Span<float3> guide_positions = guide_curves.positions();
333
334 threading::parallel_for(child_curves.curves_range(), 128, [&](const IndexRange range) {
335 Vector<float, 16> sample_lengths;
336 Vector<int, 16> sample_segments;
337 Vector<float, 16> sample_factors;
338
339 for (const int child_curve_i : range) {
340 const IndexRange points = child_points_by_curve[child_curve_i];
341 const int neighbor_count = all_neighbor_counts[child_curve_i];
342 const float3 child_up = points_up[child_curve_i];
343 BLI_assert(math::is_unit_scale(child_up));
344 const float3 &child_root_position = point_positions[child_curve_i];
345 MutableSpan<float3> child_positions = children_positions.slice(points);
346
347 child_positions.fill(child_root_position);
348 if (neighbor_count == 0) {
349 /* Creates a curve with a single point at the root position. */
350 continue;
351 }
352
353 const IndexRange neighbors_range{child_curve_i * max_neighbors, neighbor_count};
354 const Span<float> neighbor_weights = all_neighbor_weights.slice(neighbors_range);
355 const Span<int> neighbor_indices = all_neighbor_indices.slice(neighbors_range);
356
357 const bool use_direct_interpolation = use_direct_interpolation_per_child[child_curve_i];
358
359 for (const int neighbor_i : IndexRange(neighbor_count)) {
360 const int neighbor_index = neighbor_indices[neighbor_i];
361 const float neighbor_weight = neighbor_weights[neighbor_i];
362 const IndexRange guide_points = guide_points_by_curve[neighbor_index];
363 const Span<float3> neighbor_positions = guide_positions.slice(guide_points);
364 const float3 &neighbor_root = neighbor_positions.first();
365 const float3 neighbor_up = guides_up[neighbor_index];
366 BLI_assert(math::is_unit_scale(neighbor_up));
367
368 const bool is_same_up_vector = neighbor_up == child_up;
369
370 float3x3 normal_rotation;
371 if (!is_same_up_vector) {
372 rotation_between_vecs_to_mat3(normal_rotation.ptr(), neighbor_up, child_up);
373 }
374
375 if (use_direct_interpolation) {
376 /* In this method, the control point positions are interpolated directly instead of
377 * looking at evaluated points. This is much faster than the method below but only works
378 * if all guides have the same number of points. */
379 for (const int i : IndexRange(points.size())) {
380 const float3 &neighbor_pos = neighbor_positions[i];
381 const float3 relative_to_root = neighbor_pos - neighbor_root;
382 float3 rotated_relative = relative_to_root;
383 if (!is_same_up_vector) {
384 rotated_relative = normal_rotation * rotated_relative;
385 }
386 child_positions[i] += neighbor_weight * rotated_relative;
387 }
388 }
389 else {
390 /* This method is used when guide curves have different amounts of control points. In
391 * this case, some additional interpolation is necessary compared to the method above. */
392
393 const IndexRange guide_offsets = parameterized_guide_offsets[neighbor_index];
394
395 if (guide_offsets.is_empty()) {
396 /* Single point curve. */
397 float3 rotated_relative = neighbor_root;
398 if (!is_same_up_vector) {
399 rotated_relative = normal_rotation * rotated_relative;
400 }
401 const float3 global_pos = rotated_relative * neighbor_weight;
402 for (float3 &position : child_positions) {
403 position += global_pos;
404 }
405 continue;
406 }
407
408 const Span<float> lengths = parameterized_guide_lengths.slice(guide_offsets);
409 const float neighbor_length = lengths.last();
410
411 sample_lengths.reinitialize(points.size());
412 const float sample_length_factor = math::safe_divide(neighbor_length,
413 float(points.size() - 1));
414 for (const int i : sample_lengths.index_range()) {
415 sample_lengths[i] = i * sample_length_factor;
416 }
417
418 sample_segments.reinitialize(points.size());
419 sample_factors.reinitialize(points.size());
420 length_parameterize::sample_at_lengths(
421 lengths, sample_lengths, sample_segments, sample_factors);
422
423 for (const int i : IndexRange(points.size())) {
424 const int segment = sample_segments[i];
425 const float factor = sample_factors[i];
426 const float3 sample_pos = math::interpolate(
427 neighbor_positions[segment], neighbor_positions[segment + 1], factor);
428 const float3 relative_to_root = sample_pos - neighbor_root;
429 float3 rotated_relative = relative_to_root;
430 if (!is_same_up_vector) {
431 rotated_relative = normal_rotation * rotated_relative;
432 }
433 child_positions[i] += neighbor_weight * rotated_relative;
434 }
435 }
436 }
437 }
438 });
439
440 /* Can only create catmull rom curves for now. */
441 child_curves.fill_curve_types(CURVE_TYPE_CATMULL_ROM);
442}
443
448 const bke::CurvesGeometry &guide_curves,
449 const AttributeAccessor &point_attributes,
450 const AttributeFilter &attribute_filter,
451 const int max_neighbors,
452 const Span<int> all_neighbor_indices,
453 const Span<float> all_neighbor_weights,
454 const Span<int> all_neighbor_counts,
455 const OffsetIndices<int> parameterized_guide_offsets,
456 const Span<float> parameterized_guide_lengths,
457 const Span<bool> use_direct_interpolation_per_child)
458{
459 const AttributeAccessor guide_curve_attributes = guide_curves.attributes();
460 MutableAttributeAccessor children_attributes = child_curves.attributes_for_write();
461
462 const OffsetIndices child_points_by_curve = child_curves.points_by_curve();
463 const OffsetIndices guide_points_by_curve = guide_curves.points_by_curve();
464
465 /* Interpolate attributes from guide curves to child curves. Attributes stay on the same domain
466 * that they had on the guides. */
467 guide_curve_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
468 if (attribute_filter.allow_skip(iter.name)) {
469 return;
470 }
471 const bke::AttrType type = iter.data_type;
472 if (type == bke::AttrType::String) {
473 return;
474 }
475 if (iter.is_builtin && !ELEM(iter.name, "radius", "tilt", "resolution", "cyclic")) {
476 return;
477 }
478
479 if (iter.domain == AttrDomain::Curve) {
480 const GVArraySpan src_generic = *iter.get(AttrDomain::Curve, type);
481
482 GSpanAttributeWriter dst_generic = children_attributes.lookup_or_add_for_write_only_span(
483 iter.name, AttrDomain::Curve, type);
484 if (!dst_generic) {
485 return;
486 }
487 bke::attribute_math::convert_to_static_type(type, [&](auto dummy) {
488 using T = decltype(dummy);
489 const Span<T> src = src_generic.typed<T>();
490 MutableSpan<T> dst = dst_generic.span.typed<T>();
491
493 threading::parallel_for(child_curves.curves_range(), 256, [&](const IndexRange range) {
494 for (const int child_curve_i : range) {
495 const int neighbor_count = all_neighbor_counts[child_curve_i];
496 const IndexRange neighbors_range{child_curve_i * max_neighbors, neighbor_count};
497 const Span<float> neighbor_weights = all_neighbor_weights.slice(neighbors_range);
498 const Span<int> neighbor_indices = all_neighbor_indices.slice(neighbors_range);
499
500 for (const int neighbor_i : IndexRange(neighbor_count)) {
501 const int neighbor_index = neighbor_indices[neighbor_i];
502 const float neighbor_weight = neighbor_weights[neighbor_i];
503 mixer.mix_in(child_curve_i, src[neighbor_index], neighbor_weight);
504 }
505 }
506 mixer.finalize(range);
507 });
508 });
509
510 dst_generic.finish();
511 }
512 else {
513 BLI_assert(iter.domain == AttrDomain::Point);
514 const GVArraySpan src_generic = *iter.get(AttrDomain::Point, type);
515 GSpanAttributeWriter dst_generic = children_attributes.lookup_or_add_for_write_only_span(
516 iter.name, AttrDomain::Point, type);
517 if (!dst_generic) {
518 return;
519 }
520
521 bke::attribute_math::convert_to_static_type(type, [&](auto dummy) {
522 using T = decltype(dummy);
523 const Span<T> src = src_generic.typed<T>();
524 MutableSpan<T> dst = dst_generic.span.typed<T>();
525
527 threading::parallel_for(child_curves.curves_range(), 256, [&](const IndexRange range) {
528 Vector<float, 16> sample_lengths;
529 Vector<int, 16> sample_segments;
530 Vector<float, 16> sample_factors;
531 for (const int child_curve_i : range) {
532 const IndexRange points = child_points_by_curve[child_curve_i];
533 const int neighbor_count = all_neighbor_counts[child_curve_i];
534 const IndexRange neighbors_range{child_curve_i * max_neighbors, neighbor_count};
535 const Span<float> neighbor_weights = all_neighbor_weights.slice(neighbors_range);
536 const Span<int> neighbor_indices = all_neighbor_indices.slice(neighbors_range);
537 const bool use_direct_interpolation =
538 use_direct_interpolation_per_child[child_curve_i];
539
540 for (const int neighbor_i : IndexRange(neighbor_count)) {
541 const int neighbor_index = neighbor_indices[neighbor_i];
542 const float neighbor_weight = neighbor_weights[neighbor_i];
543 const IndexRange guide_points = guide_points_by_curve[neighbor_index];
544
545 if (use_direct_interpolation) {
546 for (const int i : IndexRange(points.size())) {
547 mixer.mix_in(points[i], src[guide_points[i]], neighbor_weight);
548 }
549 }
550 else {
551 const IndexRange guide_offsets = parameterized_guide_offsets[neighbor_index];
552 if (guide_offsets.is_empty()) {
553 /* Single point curve. */
554 const T &curve_value = src[guide_points.first()];
555 for (const int i : points) {
556 mixer.mix_in(i, curve_value, neighbor_weight);
557 }
558 continue;
559 }
560
561 const Span<float> lengths = parameterized_guide_lengths.slice(guide_offsets);
562 const float neighbor_length = lengths.last();
563
564 sample_lengths.reinitialize(points.size());
565 const float sample_length_factor = math::safe_divide(neighbor_length,
566 float(points.size() - 1));
567 for (const int i : sample_lengths.index_range()) {
568 sample_lengths[i] = i * sample_length_factor;
569 }
570
571 sample_segments.reinitialize(points.size());
572 sample_factors.reinitialize(points.size());
573 length_parameterize::sample_at_lengths(
574 lengths, sample_lengths, sample_segments, sample_factors);
575
576 for (const int i : IndexRange(points.size())) {
577 const int segment = sample_segments[i];
578 const float factor = sample_factors[i];
579 const T value = math::interpolate(
580 src[guide_points[segment]], src[guide_points[segment + 1]], factor);
581 mixer.mix_in(points[i], value, neighbor_weight);
582 }
583 }
584 }
585 }
586 mixer.finalize(child_points_by_curve[range]);
587 });
588 });
589
590 dst_generic.finish();
591 }
592 });
593
594 /* Interpolate attributes from the points to child curves. All attributes become curve
595 * attributes. */
596 point_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
597 if (iter.is_builtin && !children_attributes.is_builtin(iter.name)) {
598 return;
599 }
600 if (guide_curve_attributes.contains(iter.name)) {
601 return;
602 }
603 if (attribute_filter.allow_skip(iter.name)) {
604 return;
605 }
606 if (iter.data_type == bke::AttrType::String) {
607 return;
608 }
609
610 const GAttributeReader src = iter.get();
611 if (src.sharing_info && src.varray.is_span()) {
612 const bke::AttributeInitShared init(src.varray.get_internal_span().data(),
613 *src.sharing_info);
614 children_attributes.add(iter.name, AttrDomain::Curve, iter.data_type, init);
615 }
616 else {
617 children_attributes.add(
618 iter.name, AttrDomain::Curve, iter.data_type, bke::AttributeInitVArray(src.varray));
619 }
620 });
621}
622
624 const std::optional<StringRef> &weight_attribute_id,
625 const std::optional<StringRef> &index_attribute_id,
626 const int max_neighbors,
627 const Span<int> all_neighbor_counts,
628 const Span<int> all_neighbor_indices,
629 const Span<float> all_neighbor_weights)
630{
631 if (!weight_attribute_id && !index_attribute_id) {
632 return;
633 }
634 SpanAttributeWriter<float> weight_attribute;
635 if (weight_attribute_id) {
636 weight_attribute =
638 *weight_attribute_id, AttrDomain::Curve);
639 }
640 SpanAttributeWriter<int> index_attribute;
641 if (index_attribute_id) {
642 index_attribute = child_curves.attributes_for_write().lookup_or_add_for_write_only_span<int>(
643 *index_attribute_id, AttrDomain::Curve);
644 }
645 threading::parallel_for(child_curves.curves_range(), 512, [&](const IndexRange range) {
646 for (const int child_curve_i : range) {
647 const int neighbor_count = all_neighbor_counts[child_curve_i];
648
649 int closest_index;
650 float closest_weight;
651 if (neighbor_count == 0) {
652 closest_index = 0;
653 closest_weight = 0.0f;
654 }
655 else {
656 const IndexRange neighbors_range{child_curve_i * max_neighbors, neighbor_count};
657 const Span<float> neighbor_weights = all_neighbor_weights.slice(neighbors_range);
658 const Span<int> neighbor_indices = all_neighbor_indices.slice(neighbors_range);
659 const int max_index = std::max_element(neighbor_weights.begin(), neighbor_weights.end()) -
660 neighbor_weights.begin();
661 closest_index = neighbor_indices[max_index];
662 closest_weight = neighbor_weights[max_index];
663 }
664 if (index_attribute) {
665 index_attribute.span[child_curve_i] = closest_index;
666 }
667 if (weight_attribute) {
668 weight_attribute.span[child_curve_i] = closest_weight;
669 }
670 }
671 });
672 if (index_attribute) {
673 index_attribute.finish();
674 }
675 if (weight_attribute) {
676 weight_attribute.finish();
677 }
678}
679
681 const Curves &guide_curves_id,
682 const AttributeAccessor &point_attributes,
683 const VArray<float3> &guides_up,
684 const VArray<float3> &points_up,
685 const VArray<int> &guide_group_ids,
686 const VArray<int> &point_group_ids,
687 const int max_neighbors,
688 const AttributeFilter &attribute_filter,
689 const std::optional<StringRef> &index_attribute_id,
690 const std::optional<StringRef> &weight_attribute_id)
691{
692 const bke::CurvesGeometry &guide_curves = guide_curves_id.geometry.wrap();
693
694 const MultiValueMap<int, int> guides_by_group = separate_guides_by_group(guide_group_ids);
695 const Map<int, int> points_per_curve_by_group = compute_points_per_curve_by_group(
696 guides_by_group, guide_curves);
697
698 Map<int, KDTree_3d *> kdtrees = build_kdtrees_for_root_positions(guides_by_group, guide_curves);
699 BLI_SCOPED_DEFER([&]() {
700 for (KDTree_3d *kdtree : kdtrees.values()) {
701 BLI_kdtree_3d_free(kdtree);
702 }
703 });
704
705 const VArraySpan point_positions = *point_attributes.lookup<float3>("position");
706 const int num_child_curves = point_attributes.domain_size(AttrDomain::Point);
707
708 /* The set of guides per child are stored in a flattened array to allow fast access, reduce
709 * memory consumption and reduce number of allocations. */
710 Array<int> all_neighbor_indices(num_child_curves * max_neighbors);
711 Array<float> all_neighbor_weights(num_child_curves * max_neighbors);
712 Array<int> all_neighbor_counts(num_child_curves);
713
714 find_neighbor_guides(point_positions,
715 point_group_ids,
716 kdtrees,
717 guides_by_group,
718 max_neighbors,
719 all_neighbor_indices,
720 all_neighbor_weights,
721 all_neighbor_counts);
722
723 Curves *child_curves_id = bke::curves_new_nomain(0, num_child_curves);
724 bke::CurvesGeometry &child_curves = child_curves_id->geometry.wrap();
725 MutableSpan<int> children_curve_offsets = child_curves.offsets_for_write();
726
727 Array<bool> use_direct_interpolation_per_child(num_child_curves);
729 point_group_ids,
730 points_per_curve_by_group,
731 all_neighbor_indices,
732 all_neighbor_weights,
733 all_neighbor_counts,
734 max_neighbors,
735 children_curve_offsets.drop_back(1),
736 use_direct_interpolation_per_child);
737 offset_indices::accumulate_counts_to_offsets(children_curve_offsets);
738 const int num_child_points = children_curve_offsets.last();
739 child_curves.resize(num_child_points, num_child_curves);
740
741 /* Stores parameterization of all guide curves in flat arrays. */
742 Array<int> parameterized_guide_offsets;
743 Array<float> parameterized_guide_lengths;
745 guide_curves, parameterized_guide_offsets, parameterized_guide_lengths);
746
747 interpolate_curve_shapes(child_curves,
748 guide_curves,
749 max_neighbors,
750 all_neighbor_indices,
751 all_neighbor_weights,
752 all_neighbor_counts,
753 guides_up,
754 points_up,
755 point_positions,
756 OffsetIndices<int>(parameterized_guide_offsets),
757 parameterized_guide_lengths,
758 use_direct_interpolation_per_child);
759 interpolate_curve_attributes(child_curves,
760 guide_curves,
761 point_attributes,
762 attribute_filter,
763 max_neighbors,
764 all_neighbor_indices,
765 all_neighbor_weights,
766 all_neighbor_counts,
767 OffsetIndices<int>(parameterized_guide_offsets),
768 parameterized_guide_lengths,
769 use_direct_interpolation_per_child);
770
771 store_output_attributes(child_curves,
772 weight_attribute_id,
773 index_attribute_id,
774 max_neighbors,
775 all_neighbor_counts,
776 all_neighbor_indices,
777 all_neighbor_weights);
778
779 if (guide_curves_id.mat != nullptr) {
780 child_curves_id->mat = static_cast<Material **>(MEM_dupallocN(guide_curves_id.mat));
781 child_curves_id->totcol = guide_curves_id.totcol;
782 }
783
785
786 return GeometrySet::from_curves(child_curves_id);
787}
788
790{
791 GeometrySet guide_curves_geometry = params.extract_input<GeometrySet>("Guide Curves");
792 const GeometrySet points_geometry = params.extract_input<GeometrySet>("Points");
793
794 if (!guide_curves_geometry.has_curves() ||
795 guide_curves_geometry.get_curves()->geometry.curve_num == 0)
796 {
797 params.set_default_remaining_outputs();
798 return;
799 }
800 const GeometryComponent *points_component = points_geometry.get_component<PointCloudComponent>();
801 if (points_component == nullptr) {
802 points_component = points_geometry.get_component<MeshComponent>();
803 }
804 if (points_component == nullptr || points_geometry.is_empty()) {
805 params.set_default_remaining_outputs();
806 return;
807 }
808
809 const int max_neighbors = std::max<int>(1, params.extract_input<int>("Max Neighbors"));
810
811 static auto normalize_fn = mf::build::SI1_SO<float3, float3>(
812 "Normalize",
813 [](const float3 &v) { return math::normalize(v); },
814 mf::build::exec_presets::AllSpanOrSingle());
815
816 /* Normalize up fields so that is done as part of field evaluation. */
817 Field<float3> guides_up_field(
818 FieldOperation::from(normalize_fn, {params.extract_input<Field<float3>>("Guide Up")}));
819 Field<float3> points_up_field(
820 FieldOperation::from(normalize_fn, {params.extract_input<Field<float3>>("Point Up")}));
821
822 Field<int> guide_group_field = params.extract_input<Field<int>>("Guide Group ID");
823 Field<int> point_group_field = params.extract_input<Field<int>>("Point Group ID");
824
825 const Curves &guide_curves_id = *guide_curves_geometry.get_curves();
826
827 const bke::CurvesFieldContext curves_context{guide_curves_id, AttrDomain::Curve};
828 fn::FieldEvaluator curves_evaluator{curves_context, guide_curves_id.geometry.curve_num};
829 curves_evaluator.add(guides_up_field);
830 curves_evaluator.add(guide_group_field);
831 curves_evaluator.evaluate();
832 const VArray<float3> guides_up = curves_evaluator.get_evaluated<float3>(0);
833 const VArray<int> guide_group_ids = curves_evaluator.get_evaluated<int>(1);
834
835 const bke::GeometryFieldContext points_context(*points_component, AttrDomain::Point);
836 fn::FieldEvaluator points_evaluator{points_context,
837 points_component->attribute_domain_size(AttrDomain::Point)};
838 points_evaluator.add(points_up_field);
839 points_evaluator.add(point_group_field);
840 points_evaluator.evaluate();
841 const VArray<float3> points_up = points_evaluator.get_evaluated<float3>(0);
842 const VArray<int> point_group_ids = points_evaluator.get_evaluated<int>(1);
843
844 const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Curves");
845
846 std::optional<std::string> index_attribute_id =
847 params.get_output_anonymous_attribute_id_if_needed("Closest Index");
848 std::optional<std::string> weight_attribute_id =
849 params.get_output_anonymous_attribute_id_if_needed("Closest Weight");
850
851 GeometrySet new_curves = generate_interpolated_curves(guide_curves_id,
852 *points_component->attributes(),
853 guides_up,
854 points_up,
855 guide_group_ids,
856 point_group_ids,
857 max_neighbors,
858 attribute_filter,
859 index_attribute_id,
860 weight_attribute_id);
861
863 if (const auto *curve_edit_data =
864 guide_curves_geometry.get_component<GeometryComponentEditData>())
865 {
866 new_curves.add(*curve_edit_data);
867 }
868 new_curves.name = guide_curves_geometry.name;
869
870 params.set_output("Curves", std::move(new_curves));
871}
872
873static void node_register()
874{
875 static blender::bke::bNodeType ntype;
876
877 geo_node_type_base(&ntype, "GeometryNodeInterpolateCurves", GEO_NODE_INTERPOLATE_CURVES);
878 ntype.ui_name = "Interpolate Curves";
879 ntype.ui_description = "Generate new curves on points by interpolating between existing curves";
880 ntype.enum_name_legacy = "INTERPOLATE_CURVES";
883 ntype.declare = node_declare;
885}
886NOD_REGISTER_NODE(node_register)
887
888} // namespace blender::nodes::node_geo_interpolate_curves_cc
Low-level operations for curves.
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#define GEO_NODE_INTERPOLATE_CURVES
#define BLI_assert(a)
Definition BLI_assert.h:46
A KD-tree for nearest neighbor search.
#define BLI_SCOPED_DEFER(function_to_defer)
#define ELEM(...)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
ATTR_WARN_UNUSED_RESULT const BMVert * v
void init()
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:419
MutableSpan< T > typed() const
Span< T > typed() const
ValueIterator values() const &
Definition BLI_map.hh:884
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
MapType::ItemIterator items() const
void add(const Key &key, const Value &value)
constexpr MutableSpan drop_back(const int64_t n) const
Definition BLI_span.hh:618
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:689
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
IndexRange index_range() const
void foreach_attribute(const FunctionRef< void(const AttributeIter &)> fn) const
GAttributeReader lookup(const StringRef attribute_id) const
int domain_size(const AttrDomain domain) const
GAttributeReader get() const
MutableSpan< float3 > positions_for_write()
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
MutableAttributeAccessor attributes_for_write()
Span< float3 > positions() const
void resize(int points_num, int curves_num)
AttributeAccessor attributes() const
MutableSpan< int > offsets_for_write()
int attribute_domain_size(AttrDomain domain) const
virtual std::optional< AttributeAccessor > attributes() const
virtual bool is_empty() const
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, AttrType data_type)
int add(GField field, GVArray *varray_ptr)
Definition field.cc:751
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:448
static std::shared_ptr< FieldOperation > from(std::shared_ptr< const mf::MultiFunction > function, Vector< GField > inputs={})
Definition FN_field.hh:242
static void remember_deformed_positions_if_necessary(GeometrySet &geometry)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
#define T
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
typename DefaultMixerStruct< T >::type DefaultMixer
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
Curves * curves_new_nomain(int points_num, int curves_num)
void debug_randomize_curve_order(bke::CurvesGeometry *curves)
Definition randomize.cc:257
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
static MultiValueMap< int, int > separate_guides_by_group(const VArray< int > &guide_group_ids)
static void parameterize_guide_curves(const bke::CurvesGeometry &guide_curves, Array< int > &r_parameterized_guide_offsets, Array< float > &r_parameterized_guide_lengths)
static void node_declare(NodeDeclarationBuilder &b)
static void find_neighbor_guides(const Span< float3 > positions, const VArray< int > point_group_ids, const Map< int, KDTree_3d * > kdtrees, const MultiValueMap< int, int > &guides_by_group, const int max_neighbor_count, MutableSpan< int > r_all_neighbor_indices, MutableSpan< float > r_all_neighbor_weights, MutableSpan< int > r_all_neighbor_counts)
static void interpolate_curve_attributes(bke::CurvesGeometry &child_curves, const bke::CurvesGeometry &guide_curves, const AttributeAccessor &point_attributes, const AttributeFilter &attribute_filter, const int max_neighbors, const Span< int > all_neighbor_indices, const Span< float > all_neighbor_weights, const Span< int > all_neighbor_counts, const OffsetIndices< int > parameterized_guide_offsets, const Span< float > parameterized_guide_lengths, const Span< bool > use_direct_interpolation_per_child)
static void interpolate_curve_shapes(bke::CurvesGeometry &child_curves, const bke::CurvesGeometry &guide_curves, const int max_neighbors, const Span< int > all_neighbor_indices, const Span< float > all_neighbor_weights, const Span< int > all_neighbor_counts, const VArray< float3 > &guides_up, const VArray< float3 > &points_up, const Span< float3 > point_positions, const OffsetIndices< int > parameterized_guide_offsets, const Span< float > parameterized_guide_lengths, const Span< bool > use_direct_interpolation_per_child)
static Map< int, int > compute_points_per_curve_by_group(const MultiValueMap< int, int > &guides_by_group, const bke::CurvesGeometry &guide_curves)
static GeometrySet generate_interpolated_curves(const Curves &guide_curves_id, const AttributeAccessor &point_attributes, const VArray< float3 > &guides_up, const VArray< float3 > &points_up, const VArray< int > &guide_group_ids, const VArray< int > &point_group_ids, const int max_neighbors, const AttributeFilter &attribute_filter, const std::optional< StringRef > &index_attribute_id, const std::optional< StringRef > &weight_attribute_id)
static void compute_point_counts_per_child(const bke::CurvesGeometry &guide_curves, const VArray< int > &point_group_ids, const Map< int, int > &points_per_curve_by_group, const Span< int > all_neighbor_indices, const Span< float > all_neighbor_weights, const Span< int > all_neighbor_counts, const int max_neighbors, MutableSpan< int > r_points_per_child, MutableSpan< bool > r_use_direct_interpolation)
static void store_output_attributes(bke::CurvesGeometry &child_curves, const std::optional< StringRef > &weight_attribute_id, const std::optional< StringRef > &index_attribute_id, const int max_neighbors, const Span< int > all_neighbor_counts, const Span< int > all_neighbor_indices, const Span< float > all_neighbor_weights)
static Map< int, KDTree_3d * > build_kdtrees_for_root_positions(const MultiValueMap< int, int > &guides_by_group, const bke::CurvesGeometry &guide_curves)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
void parallel_for_each(Range &&range, const Function &function)
Definition BLI_task.hh:56
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
static void init(bNodeTree *, bNode *node)
CurvesGeometry geometry
struct Material ** mat
static GeometrySet from_curves(Curves *curves, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
bool allow_skip(const StringRef name) const
const GeometryComponent * get_component(GeometryComponent::Type component_type) const
const Curves * get_curves() const
void add(const GeometryComponent &component)
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362