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