Blender V4.5
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
9#include "BKE_curves.hh"
10
11#include "BLI_array_utils.hh"
12#include "BLI_assert.h"
14#include "BLI_math_vector.hh"
15#include "BLI_offset_indices.hh"
16#include "BLI_task.hh"
17
19
22
23namespace blender::geometry {
24
25using bke::CurvesGeometry;
26
27/* Returns a map that places each point in the sample index space.
28 * The map has one additional point at the end to simplify cyclic curve mapping. */
30 const bool cyclic,
31 const int samples_num)
32{
33 const IndexRange points = positions.index_range();
34 Array<float> sample_by_point(points.size() + 1);
35 sample_by_point[0] = 0.0f;
36 for (const int i : points.drop_front(1)) {
37 sample_by_point[i] = sample_by_point[i - 1] + math::distance(positions[i - 1], positions[i]);
38 }
39 sample_by_point.last() = cyclic ? sample_by_point[points.size() - 1] +
40 math::distance(positions.last(), positions.first()) :
41 sample_by_point[points.size() - 1];
42
43 /* If source segment lengths are zero use uniform mapping by index as a fallback. */
44 constexpr float length_epsilon = 1e-4f;
45 if (sample_by_point.last() <= length_epsilon) {
47 }
48
49 const float total_length = sample_by_point.last();
50 /* Factor for mapping segment length to sample index space. */
51 const float length_to_sample_count = math::safe_divide(float(samples_num), total_length);
52 for (float &sample_value : sample_by_point) {
53 sample_value *= length_to_sample_count;
54 }
55
56 return sample_by_point;
57}
58
59static void assign_samples_to_segments(const int num_dst_points,
60 const Span<float3> src_positions,
61 const bool cyclic,
62 MutableSpan<int> dst_sample_offsets)
63{
64 const IndexRange src_points = src_positions.index_range();
65 BLI_assert(src_points.size() > 0);
66 BLI_assert(num_dst_points > 0);
67 BLI_assert(num_dst_points >= src_points.size());
68 BLI_assert(dst_sample_offsets.size() == src_points.size() + 1);
69
70 /* Extra points of the destination curve that need to be distributed on source segments. */
71 const int num_free_samples = num_dst_points - int(src_points.size());
72 const Array<float> sample_by_point = build_point_to_sample_map(
73 src_positions, cyclic, num_free_samples);
74
75 int samples_start = 0;
76 for (const int src_point_i : src_points) {
77 dst_sample_offsets[src_point_i] = samples_start;
78
79 /* Use rounding to distribute samples equally over all segments. */
80 const int free_samples = math::round(sample_by_point[src_point_i + 1]) -
81 math::round(sample_by_point[src_point_i]);
82 samples_start += 1 + free_samples;
83 }
84
85 /* This also assigns any remaining samples in case of rounding error. */
86 dst_sample_offsets.last() = num_dst_points;
87}
88
89void sample_curve_padded(const Span<float3> positions,
90 const bool cyclic,
91 MutableSpan<int> r_indices,
92 MutableSpan<float> r_factors)
93{
94 const int num_dst_points = r_indices.size();
95 BLI_assert(r_factors.size() == num_dst_points);
96 const IndexRange src_points = positions.index_range();
97
98 if (num_dst_points == 0) {
99 return;
100 }
101 if (num_dst_points == 1) {
102 r_indices.first() = 0;
103 r_factors.first() = 0.0f;
104 return;
105 }
106
107 if (src_points.is_empty()) {
108 return;
109 }
110 if (src_points.size() == 1) {
111 r_indices.fill(0);
112 r_factors.fill(0.0f);
113 return;
114 }
115
116 /* If the destination curve has equal or more points then the excess samples are distributed
117 * equally over all the segments.
118 * If the destination curve is shorter the samples are placed equidistant along the source
119 * segments. */
120 if (num_dst_points >= src_points.size()) {
121 /* First destination point in each source segment. */
122 Array<int> dst_sample_offsets(src_points.size() + 1);
123 assign_samples_to_segments(num_dst_points, positions, cyclic, dst_sample_offsets);
124
125 OffsetIndices dst_samples_by_src_point = OffsetIndices<int>(dst_sample_offsets);
126 for (const int src_point_i : src_points.index_range()) {
127 const IndexRange samples = dst_samples_by_src_point[src_point_i];
128
129 r_indices.slice(samples).fill(src_point_i);
130 for (const int sample_i : samples.index_range()) {
131 const int sample = samples[sample_i];
132 const float factor = float(sample_i) / samples.size();
133 r_factors[sample] = factor;
134 }
135 }
136 }
137 else {
138 const Array<float> sample_by_point = build_point_to_sample_map(
139 positions, cyclic, num_dst_points - (cyclic ? 0 : 1));
140
141 for (const int src_point_i : src_points.index_range()) {
142 const float sample_start = sample_by_point[src_point_i];
143 const float sample_end = sample_by_point[src_point_i + 1];
144 const IndexRange samples = IndexRange::from_begin_end(math::ceil(sample_start),
145 math::ceil(sample_end));
146
147 for (const int sample : samples) {
148 r_indices[sample] = src_point_i;
149 r_factors[sample] = math::safe_divide(float(sample) - sample_start,
150 sample_end - sample_start);
151 }
152 }
153 if (!cyclic) {
154 r_indices.last() = src_points.size() - 1;
155 r_factors.last() = 0.0f;
156 }
157 }
158}
159
160static void reverse_samples(const int points_num,
161 MutableSpan<int> r_indices,
162 MutableSpan<float> r_factors)
163{
164 Vector<int> reverse_indices;
165 Vector<float> reverse_factors;
166 reverse_indices.reserve(r_indices.size());
167 reverse_factors.reserve(r_factors.size());
168 /* Indices in the last (cyclic) segment are also in the last segment when reversed. */
169 for (const int i : r_indices.index_range()) {
170 const int index = r_indices[i];
171 const float factor = r_factors[i];
172 const bool is_last_segment = index >= points_num - 1;
173
174 if (is_last_segment && factor > 0.0f) {
175 reverse_indices.append(points_num - 1);
176 reverse_factors.append(1.0f - factor);
177 }
178 }
179 /* Insert reversed indices except the last (cyclic) segment. */
180 for (const int i : r_indices.index_range()) {
181 const int index = r_indices[i];
182 const float factor = r_factors[i];
183 const bool is_last_segment = index >= points_num - 1;
184
185 if (factor > 0.0f) {
186 /* Skip the last (cyclic) segment, handled below. */
187 if (is_last_segment) {
188 continue;
189 }
190 reverse_indices.append(points_num - 2 - index);
191 reverse_factors.append(1.0f - r_factors[i]);
192 }
193 else {
194 /* Move factor 1.0 into the next segment. */
195 reverse_indices.append(points_num - 1 - index);
196 reverse_factors.append(0.0f);
197 }
198 }
199
200 r_indices.copy_from(reverse_indices);
201 r_factors.copy_from(reverse_factors);
202}
203
205 const int curve_index,
206 const bool cyclic,
207 const bool reverse,
208 MutableSpan<int> r_indices,
209 MutableSpan<float> r_factors)
210{
211 BLI_assert(curves.curves_range().contains(curve_index));
212 BLI_assert(r_indices.size() == r_factors.size());
213 const IndexRange points = curves.points_by_curve()[curve_index];
214 const Span<float3> positions = curves.positions().slice(points);
215
216 if (reverse) {
217 const int points_num = positions.size();
218 Array<float3> reverse_positions(points_num);
219 for (const int i : reverse_positions.index_range()) {
220 reverse_positions[i] = positions[points_num - 1 - i];
221 }
222
223 sample_curve_padded(reverse_positions, cyclic, r_indices, r_factors);
224
225 reverse_samples(points_num, r_indices, r_factors);
226 }
227 else {
228 sample_curve_padded(positions, cyclic, r_indices, r_factors);
229 }
230}
231
236static bool interpolate_attribute_to_curves(const StringRef attribute_id,
237 const std::array<int, CURVE_TYPES_NUM> &type_counts)
238{
239 if (bke::attribute_name_is_anonymous(attribute_id)) {
240 return true;
241 }
242 if (ELEM(attribute_id, "handle_type_left", "handle_type_right", "handle_left", "handle_right")) {
243 return type_counts[CURVE_TYPE_BEZIER] != 0;
244 }
245 if (ELEM(attribute_id, "nurbs_weight")) {
246 return type_counts[CURVE_TYPE_NURBS] != 0;
247 }
248 return true;
249}
250
254static bool interpolate_attribute_to_poly_curve(const StringRef attribute_id)
255{
256 static const Set<StringRef> no_interpolation{{
257 "handle_type_left",
258 "handle_type_right",
259 "handle_right",
260 "handle_left",
261 "nurbs_weight",
262 }};
263 return !no_interpolation.contains(attribute_id);
264}
265
272
277 const CurvesGeometry &src_from_curves,
278 const CurvesGeometry &src_to_curves,
279 const bke::AttrDomain domain,
280 CurvesGeometry &dst_curves)
281{
283
284 const bke::AttributeAccessor src_from_attributes = src_from_curves.attributes();
285 const bke::AttributeAccessor src_to_attributes = src_to_curves.attributes();
286 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
287 for (const int i : ids.index_range()) {
288 eCustomDataType data_type;
289
290 const GVArray src_from_attribute = *src_from_attributes.lookup(ids[i], domain);
291 if (src_from_attribute) {
292 data_type = bke::cpp_type_to_custom_data_type(src_from_attribute.type());
293
294 const GVArray src_to_attribute = *src_to_attributes.lookup(ids[i], domain, data_type);
295
296 result.src_from.append(src_from_attribute);
297 result.src_to.append(src_to_attribute ? src_to_attribute : GVArraySpan{});
298 }
299 else {
300 const GVArray src_to_attribute = *src_to_attributes.lookup(ids[i], domain);
301 /* Attribute should exist on at least one of the geometries. */
302 BLI_assert(src_to_attribute);
303
304 data_type = bke::cpp_type_to_custom_data_type(src_to_attribute.type());
305
306 result.src_from.append(GVArraySpan{});
307 result.src_to.append(src_to_attribute);
308 }
309
310 bke::GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_span(
311 ids[i], domain, data_type);
312 result.dst.append(std::move(dst_attribute));
313 }
314
315 return result;
316}
317
322 const CurvesGeometry &from_curves, const CurvesGeometry &to_curves, CurvesGeometry &dst_curves)
323{
325 auto add_attribute = [&](const bke::AttributeIter &iter) {
326 if (iter.domain != bke::AttrDomain::Point) {
327 return;
328 }
329 if (iter.data_type == CD_PROP_STRING) {
330 return;
331 }
332 if (!interpolate_attribute_to_curves(iter.name, dst_curves.curve_type_counts())) {
333 return;
334 }
335 if (!interpolate_attribute_to_poly_curve(iter.name)) {
336 return;
337 }
338 /* Position is handled differently since it has non-generic interpolation for Bezier
339 * curves and because the evaluated positions are cached for each evaluated point. */
340 if (iter.name == "position") {
341 return;
342 }
343
344 ids.add(iter.name);
345 };
346
347 from_curves.attributes().foreach_attribute(add_attribute);
348 to_curves.attributes().foreach_attribute(add_attribute);
349
350 return retrieve_attribute_spans(ids, from_curves, to_curves, bke::AttrDomain::Point, dst_curves);
351}
352
357 const CurvesGeometry &from_curves, const CurvesGeometry &to_curves, CurvesGeometry &dst_curves)
358{
360 auto add_attribute = [&](const bke::AttributeIter &iter) {
361 if (iter.domain != bke::AttrDomain::Curve) {
362 return;
363 }
364 if (iter.data_type == CD_PROP_STRING) {
365 return;
366 }
367 if (bke::attribute_name_is_anonymous(iter.name)) {
368 return;
369 }
370 /* Interpolation tool always outputs poly curves. */
371 if (iter.name == "curve_type") {
372 return;
373 }
374
375 ids.add(iter.name);
376 };
377
378 from_curves.attributes().foreach_attribute(add_attribute);
379 to_curves.attributes().foreach_attribute(add_attribute);
380
381 return retrieve_attribute_spans(ids, from_curves, to_curves, bke::AttrDomain::Curve, dst_curves);
382}
383
384/* Resample a span of attribute values from source curves to a destination buffer. */
385static void sample_curve_attribute(const bke::CurvesGeometry &src_curves,
386 const Span<int> src_curve_indices,
387 const OffsetIndices<int> dst_points_by_curve,
388 const GSpan src_data,
389 const IndexMask &dst_curve_mask,
390 const Span<int> dst_sample_indices,
391 const Span<float> dst_sample_factors,
392 GMutableSpan dst_data)
393{
394 const CPPType &type = src_data.type();
395 BLI_assert(dst_data.type() == type);
396
397 const OffsetIndices<int> src_points_by_curve = src_curves.points_by_curve();
398 const OffsetIndices<int> src_evaluated_points_by_curve = src_curves.evaluated_points_by_curve();
399 const VArray<int8_t> curve_types = src_curves.curve_types();
400
401#ifndef NDEBUG
402 const int dst_points_num = dst_data.size();
403 BLI_assert(dst_sample_indices.size() == dst_points_num);
404 BLI_assert(dst_sample_factors.size() == dst_points_num);
405#endif
406
407 bke::attribute_math::convert_to_static_type(type, [&](auto dummy) {
408 using T = decltype(dummy);
409 Span<T> src = src_data.typed<T>();
410 MutableSpan<T> dst = dst_data.typed<T>();
411
412 Vector<T> evaluated_data;
413 dst_curve_mask.foreach_index([&](const int i_dst_curve, const int pos) {
414 const int i_src_curve = src_curve_indices[pos];
415 if (i_src_curve < 0) {
416 return;
417 }
418
419 const IndexRange src_points = src_points_by_curve[i_src_curve];
420 const IndexRange dst_points = dst_points_by_curve[i_dst_curve];
421
422 if (curve_types[i_src_curve] == CURVE_TYPE_POLY) {
424 dst_sample_indices.slice(dst_points),
425 dst_sample_factors.slice(dst_points),
426 dst.slice(dst_points));
427 }
428 else {
429 const IndexRange src_evaluated_points = src_evaluated_points_by_curve[i_src_curve];
430 evaluated_data.reinitialize(src_evaluated_points.size());
431 src_curves.interpolate_to_evaluated(
432 i_src_curve, src.slice(src_points), evaluated_data.as_mutable_span());
433 length_parameterize::interpolate(evaluated_data.as_span(),
434 dst_sample_indices.slice(dst_points),
435 dst_sample_factors.slice(dst_points),
436 dst.slice(dst_points));
437 }
438 });
439 });
440}
441
442template<typename T>
443static void mix_arrays(const Span<T> from,
444 const Span<T> to,
445 const float mix_factor,
446 const MutableSpan<T> dst)
447{
448 if (mix_factor == 0.0f) {
449 dst.copy_from(from);
450 }
451 else if (mix_factor == 1.0f) {
452 dst.copy_from(to);
453 }
454 else {
455 for (const int i : dst.index_range()) {
456 dst[i] = math::interpolate(from[i], to[i], mix_factor);
457 }
458 }
459}
460
461static void mix_arrays(const GSpan src_from,
462 const GSpan src_to,
463 const Span<float> mix_factors,
464 const IndexMask &selection,
465 const GMutableSpan dst)
466{
468 using T = decltype(dummy);
469 const Span<T> from = src_from.typed<T>();
470 const Span<T> to = src_to.typed<T>();
471 const MutableSpan<T> dst_typed = dst.typed<T>();
472 selection.foreach_index(GrainSize(512), [&](const int curve) {
473 const float mix_factor = mix_factors[curve];
474 if (mix_factor == 0.0f) {
475 dst_typed[curve] = from[curve];
476 }
477 else if (mix_factor == 1.0f) {
478 dst_typed[curve] = to[curve];
479 }
480 else {
481 dst_typed[curve] = math::interpolate(from[curve], to[curve], mix_factor);
482 }
483 });
484 });
485}
486
487static void mix_arrays(const GSpan src_from,
488 const GSpan src_to,
489 const Span<float> mix_factors,
490 const IndexMask &group_selection,
491 const OffsetIndices<int> groups,
492 const GMutableSpan dst)
493{
494 group_selection.foreach_index(GrainSize(32), [&](const int curve) {
495 const IndexRange range = groups[curve];
497 using T = decltype(dummy);
498 const Span<T> from = src_from.typed<T>();
499 const Span<T> to = src_to.typed<T>();
500 const MutableSpan<T> dst_typed = dst.typed<T>();
501 mix_arrays(from.slice(range), to.slice(range), mix_factors[curve], dst_typed.slice(range));
502 });
503 });
504}
505
507 const CurvesGeometry &to_curves,
508 const Span<int> from_curve_indices,
509 const Span<int> to_curve_indices,
510 const Span<int> from_sample_indices,
511 const Span<int> to_sample_indices,
512 const Span<float> from_sample_factors,
513 const Span<float> to_sample_factors,
514 const IndexMask &dst_curve_mask,
515 const float mix_factor,
516 CurvesGeometry &dst_curves,
517 IndexMaskMemory &memory)
518{
519 BLI_assert(from_curve_indices.size() == dst_curve_mask.size());
520 BLI_assert(to_curve_indices.size() == dst_curve_mask.size());
521 BLI_assert(from_sample_indices.size() == dst_curves.points_num());
522 BLI_assert(to_sample_indices.size() == dst_curves.points_num());
523 BLI_assert(from_sample_factors.size() == dst_curves.points_num());
524 BLI_assert(to_sample_factors.size() == dst_curves.points_num());
525
526 if (from_curves.is_empty() || to_curves.is_empty()) {
527 return;
528 }
529
530 const Span<float3> from_evaluated_positions = from_curves.evaluated_positions();
531 const Span<float3> to_evaluated_positions = to_curves.evaluated_positions();
532
533 /* All resampled curves are poly curves. */
534 dst_curves.fill_curve_types(dst_curve_mask, CURVE_TYPE_POLY);
535
536 MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
537
539 from_curves, to_curves, dst_curves);
541 from_curves, to_curves, dst_curves);
542
543 const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
544
545 Array<bool> mix_from_to(dst_curves.curves_num());
546 Array<bool> exclusive_from(dst_curves.curves_num());
547 Array<bool> exclusive_to(dst_curves.curves_num());
548 Array<float> mix_factors(dst_curves.curves_num());
549 dst_curve_mask.foreach_index(GrainSize(512), [&](const int i_dst_curve, const int pos) {
550 const int i_from_curve = from_curve_indices[pos];
551 const int i_to_curve = to_curve_indices[pos];
552 if (i_from_curve >= 0 && i_to_curve >= 0) {
553 mix_factors[i_dst_curve] = mix_factor;
554 mix_from_to[i_dst_curve] = true;
555 exclusive_from[i_dst_curve] = false;
556 exclusive_to[i_dst_curve] = false;
557 }
558 else if (i_to_curve >= 0) {
559 mix_factors[i_dst_curve] = 1.0f;
560 mix_from_to[i_dst_curve] = false;
561 exclusive_from[i_dst_curve] = false;
562 exclusive_to[i_dst_curve] = true;
563 }
564 else {
565 mix_factors[i_dst_curve] = 0.0f;
566 mix_from_to[i_dst_curve] = false;
567 exclusive_from[i_dst_curve] = true;
568 exclusive_to[i_dst_curve] = false;
569 }
570 });
571
572 /* Curve mask contains indices that may not be valid for both "from" and "to" curves. These need
573 * to be filtered out before use with the generic array utils. These masks are exclusive so that
574 * each element is only mixed in by one mask. */
575 const IndexMask mix_curve_mask = IndexMask::from_bools(dst_curve_mask, mix_from_to, memory);
576 const IndexMask from_curve_mask = IndexMask::from_bools(dst_curve_mask, exclusive_from, memory);
577 const IndexMask to_curve_mask = IndexMask::from_bools(dst_curve_mask, exclusive_to, memory);
578
579 /* For every attribute, evaluate attributes from every curve in the range in the original
580 * curve's "evaluated points", then use linear interpolation to sample to the result. */
581 for (const int i_attribute : point_attributes.dst.index_range()) {
582 /* Attributes that exist already on another domain can not be written to. */
583 if (!point_attributes.dst[i_attribute]) {
584 continue;
585 }
586
587 const GSpan src_from = point_attributes.src_from[i_attribute];
588 const GSpan src_to = point_attributes.src_to[i_attribute];
589 GMutableSpan dst = point_attributes.dst[i_attribute].span;
590
591 /* Mix factors depend on which of the from/to curves geometries has attribute data. If
592 * only one geometry has attribute data it gets the full mix weight. */
593 if (!src_from.is_empty() && !src_to.is_empty()) {
594 GArray<> from_samples(dst.type(), dst.size());
595 GArray<> to_samples(dst.type(), dst.size());
596 sample_curve_attribute(from_curves,
597 from_curve_indices,
598 dst_points_by_curve,
599 src_from,
600 dst_curve_mask,
601 from_sample_indices,
602 from_sample_factors,
603 from_samples);
604 sample_curve_attribute(to_curves,
605 to_curve_indices,
606 dst_points_by_curve,
607 src_to,
608 dst_curve_mask,
609 to_sample_indices,
610 to_sample_factors,
611 to_samples);
612 mix_arrays(from_samples, to_samples, mix_factors, dst_curve_mask, dst_points_by_curve, dst);
613 }
614 else if (!src_from.is_empty()) {
615 sample_curve_attribute(from_curves,
616 from_curve_indices,
617 dst_points_by_curve,
618 src_from,
619 dst_curve_mask,
620 from_sample_indices,
621 from_sample_factors,
622 dst);
623 }
624 else if (!src_to.is_empty()) {
625 sample_curve_attribute(to_curves,
626 to_curve_indices,
627 dst_points_by_curve,
628 src_to,
629 dst_curve_mask,
630 to_sample_indices,
631 to_sample_factors,
632 dst);
633 }
634 }
635
636 {
637 Array<float3> from_samples(dst_positions.size());
638 Array<float3> to_samples(dst_positions.size());
639
640 /* Interpolate the evaluated positions to the resampled curves. */
641 sample_curve_attribute(from_curves,
642 from_curve_indices,
643 dst_points_by_curve,
644 from_evaluated_positions,
645 dst_curve_mask,
646 from_sample_indices,
647 from_sample_factors,
648 from_samples.as_mutable_span());
649 sample_curve_attribute(to_curves,
650 to_curve_indices,
651 dst_points_by_curve,
652 to_evaluated_positions,
653 dst_curve_mask,
654 to_sample_indices,
655 to_sample_factors,
656 to_samples.as_mutable_span());
657
658 mix_arrays(from_samples.as_span(),
659 to_samples.as_span(),
660 mix_factors,
661 dst_curve_mask,
662 dst_points_by_curve,
663 dst_positions);
664 }
665
666 for (const int i_attribute : curve_attributes.dst.index_range()) {
667 /* Attributes that exist already on another domain can not be written to. */
668 if (!curve_attributes.dst[i_attribute]) {
669 continue;
670 }
671
672 const GSpan src_from = curve_attributes.src_from[i_attribute];
673 const GSpan src_to = curve_attributes.src_to[i_attribute];
674 GMutableSpan dst = curve_attributes.dst[i_attribute].span;
675
676 /* Only mix "safe" attribute types for now. Other types (int, bool, etc.) are just copied from
677 * the first curve of each pair. */
678 const bool can_mix_attribute = ELEM(bke::cpp_type_to_custom_data_type(dst.type()),
682 if (can_mix_attribute && !src_from.is_empty() && !src_to.is_empty()) {
683 array_utils::copy(GVArray::ForSpan(src_from), from_curve_mask, dst);
684 array_utils::copy(GVArray::ForSpan(src_to), to_curve_mask, dst);
685
686 GArray<> from_samples(dst.type(), dst.size());
687 GArray<> to_samples(dst.type(), dst.size());
688 array_utils::copy(GVArray::ForSpan(src_from), mix_curve_mask, from_samples);
689 array_utils::copy(GVArray::ForSpan(src_to), mix_curve_mask, to_samples);
690 mix_arrays(from_samples, to_samples, mix_factors, mix_curve_mask, dst);
691 }
692 else if (!src_from.is_empty()) {
693 array_utils::copy(GVArray::ForSpan(src_from), from_curve_mask, dst);
694 array_utils::copy(GVArray::ForSpan(src_from), mix_curve_mask, dst);
695 }
696 else if (!src_to.is_empty()) {
697 array_utils::copy(GVArray::ForSpan(src_to), to_curve_mask, dst);
698 array_utils::copy(GVArray::ForSpan(src_to), mix_curve_mask, dst);
699 }
700 }
701
702 for (bke::GSpanAttributeWriter &attribute : point_attributes.dst) {
703 attribute.finish();
704 }
705 for (bke::GSpanAttributeWriter &attribute : curve_attributes.dst) {
706 attribute.finish();
707 }
708}
709
711 const int curve_index,
712 const bool cyclic,
713 const bool reverse,
714 MutableSpan<int> r_segment_indices,
715 MutableSpan<float> r_factors)
716{
717 const Span<float> segment_lengths = curves.evaluated_lengths_for_curve(curve_index, cyclic);
718 if (segment_lengths.is_empty()) {
719 /* Handle curves with only one evaluated point. */
720 r_segment_indices.fill(0);
721 r_factors.fill(0.0f);
722 return;
723 }
724
725 if (reverse) {
727 segment_lengths, !cyclic, r_segment_indices, r_factors);
728 }
729 else {
730 length_parameterize::sample_uniform(segment_lengths, !cyclic, r_segment_indices, r_factors);
731 }
732};
733
734void interpolate_curves(const CurvesGeometry &from_curves,
735 const CurvesGeometry &to_curves,
736 const Span<int> from_curve_indices,
737 const Span<int> to_curve_indices,
738 const IndexMask &dst_curve_mask,
739 const Span<bool> dst_curve_flip_direction,
740 const float mix_factor,
741 CurvesGeometry &dst_curves,
742 IndexMaskMemory &memory)
743{
744 const VArray<bool> from_curves_cyclic = from_curves.cyclic();
745 const VArray<bool> to_curves_cyclic = to_curves.cyclic();
746 const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
747
748 /* Sampling arbitrary attributes works by first interpolating them to the curve's standard
749 * "evaluated points" and then interpolating that result with the uniform samples. This is
750 * potentially wasteful when down-sampling a curve to many fewer points. There are two possible
751 * solutions: only sample the necessary points for interpolation, or first sample curve
752 * parameter/segment indices and evaluate the curve directly. */
753 Array<int> from_sample_indices(dst_curves.points_num());
754 Array<int> to_sample_indices(dst_curves.points_num());
755 Array<float> from_sample_factors(dst_curves.points_num());
756 Array<float> to_sample_factors(dst_curves.points_num());
757
758 from_curves.ensure_evaluated_lengths();
759 to_curves.ensure_evaluated_lengths();
760
761 /* Gather uniform samples based on the accumulated lengths of the original curve. */
762 dst_curve_mask.foreach_index(GrainSize(32), [&](const int i_dst_curve, const int pos) {
763 const int i_from_curve = from_curve_indices[pos];
764 const int i_to_curve = to_curve_indices[pos];
765
766 const IndexRange dst_points = dst_points_by_curve[i_dst_curve];
767 /* First curve is sampled in forward direction, second curve may be reversed. */
768 sample_curve_uniform(from_curves,
769 i_from_curve,
770 from_curves_cyclic[i_from_curve],
771 false,
772 from_sample_indices.as_mutable_span().slice(dst_points),
773 from_sample_factors.as_mutable_span().slice(dst_points));
774 sample_curve_uniform(to_curves,
775 i_to_curve,
776 to_curves_cyclic[i_to_curve],
777 dst_curve_flip_direction[i_dst_curve],
778 to_sample_indices.as_mutable_span().slice(dst_points),
779 to_sample_factors.as_mutable_span().slice(dst_points));
780 });
781
783 to_curves,
784 from_curve_indices,
785 to_curve_indices,
786 from_sample_indices,
787 to_sample_indices,
788 from_sample_factors,
789 to_sample_factors,
790 dst_curve_mask,
791 mix_factor,
792 dst_curves,
793 memory);
794}
795
796} // namespace blender::geometry
Low-level operations for curves.
#define BLI_assert(a)
Definition BLI_assert.h:46
#define ELEM(...)
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_NURBS
@ CURVE_TYPE_POLY
@ CD_PROP_FLOAT
@ CD_PROP_FLOAT3
@ CD_PROP_FLOAT2
@ CD_PROP_STRING
static IndexMask from_bools(Span< bool > bools, IndexMaskMemory &memory)
Span< T > as_span() const
Definition BLI_array.hh:232
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
const T & last(const int64_t n=0) const
Definition BLI_array.hh:285
IndexRange index_range() const
Definition BLI_array.hh:349
const CPPType & type() const
MutableSpan< T > typed() const
Span< T > typed() const
const CPPType & type() const
bool is_empty() const
static GVArray ForSpan(GSpan span)
constexpr int64_t size() const
constexpr bool is_empty() const
static constexpr IndexRange from_begin_end(const int64_t begin, const int64_t end)
constexpr bool contains(int64_t value) const
constexpr IndexRange index_range() const
constexpr IndexRange drop_front(int64_t n) const
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr void fill(const T &value) const
Definition BLI_span.hh:517
constexpr T & first() const
Definition BLI_span.hh:679
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:689
bool contains(const Key &key) const
Definition BLI_set.hh:310
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr const T & first() const
Definition BLI_span.hh:315
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:325
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
Definition BLI_span.hh:260
bool add(const Key &key)
void append(const T &value)
void reserve(const int64_t min_capacity)
void foreach_attribute(const FunctionRef< void(const AttributeIter &)> fn) const
GAttributeReader lookup(const StringRef attribute_id) const
MutableSpan< float3 > positions_for_write()
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
const std::array< int, CURVE_TYPES_NUM > & curve_type_counts() const
MutableAttributeAccessor attributes_for_write()
Span< float > evaluated_lengths_for_curve(int curve_index, bool cyclic) const
void interpolate_to_evaluated(int curve_index, GSpan src, GMutableSpan dst) const
Span< float3 > positions() const
OffsetIndices< int > evaluated_points_by_curve() const
void fill_curve_types(CurveType type)
AttributeAccessor attributes() const
Span< float3 > evaluated_positions() const
VArray< int8_t > curve_types() const
VArray< bool > cyclic() const
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
void foreach_index(Fn &&fn) const
uint pos
#define T
void copy(const GVArray &src, GMutableSpan dst, int64_t grain_size=4096)
void fill_index_range(MutableSpan< T > span, const T start=0)
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
bool attribute_name_is_anonymous(const StringRef name)
eCustomDataType cpp_type_to_custom_data_type(const CPPType &type)
static AttributesForInterpolation gather_curve_attributes_to_interpolate(const CurvesGeometry &from_curves, const CurvesGeometry &to_curves, CurvesGeometry &dst_curves)
static void mix_arrays(const Span< T > from, const Span< T > to, const float mix_factor, const MutableSpan< T > dst)
static AttributesForInterpolation retrieve_attribute_spans(const Span< StringRef > ids, const CurvesGeometry &src_from_curves, const CurvesGeometry &src_to_curves, const bke::AttrDomain domain, CurvesGeometry &dst_curves)
void interpolate_curves_with_samples(const bke::CurvesGeometry &from_curves, const bke::CurvesGeometry &to_curves, Span< int > from_curve_indices, Span< int > to_curve_indices, Span< int > from_sample_indices, Span< int > to_sample_indices, Span< float > from_sample_factors, Span< float > to_sample_factors, const IndexMask &dst_curve_mask, float mix_factor, bke::CurvesGeometry &dst_curves, IndexMaskMemory &memory)
static Array< float > build_point_to_sample_map(const Span< float3 > positions, const bool cyclic, const int samples_num)
void sample_curve_padded(const Span< float3 > positions, bool cyclic, MutableSpan< int > r_indices, MutableSpan< float > r_factors)
static void sample_curve_attribute(const bke::CurvesGeometry &src_curves, const Span< int > src_curve_indices, const OffsetIndices< int > dst_points_by_curve, const GSpan src_data, const IndexMask &dst_curve_mask, const Span< int > dst_sample_indices, const Span< float > dst_sample_factors, GMutableSpan dst_data)
static void sample_curve_uniform(const bke::CurvesGeometry &curves, const int curve_index, const bool cyclic, const bool reverse, MutableSpan< int > r_segment_indices, MutableSpan< float > r_factors)
void interpolate_curves(const bke::CurvesGeometry &from_curves, const bke::CurvesGeometry &to_curves, Span< int > from_curve_indices, Span< int > to_curve_indices, const IndexMask &dst_curve_mask, Span< bool > dst_curve_flip_direction, float mix_factor, bke::CurvesGeometry &dst_curves, IndexMaskMemory &memory)
static bool interpolate_attribute_to_poly_curve(const StringRef attribute_id)
static void assign_samples_to_segments(const int num_dst_points, const Span< float3 > src_positions, const bool cyclic, MutableSpan< int > dst_sample_offsets)
static bool interpolate_attribute_to_curves(const StringRef attribute_id, const std::array< int, CURVE_TYPES_NUM > &type_counts)
static AttributesForInterpolation gather_point_attributes_to_interpolate(const CurvesGeometry &from_curves, const CurvesGeometry &to_curves, CurvesGeometry &dst_curves)
static void reverse_samples(const int points_num, MutableSpan< int > r_indices, MutableSpan< float > r_factors)
void sample_uniform_reverse(Span< float > accumulated_segment_lengths, bool include_first_point, MutableSpan< int > r_segment_indices, MutableSpan< float > r_factors)
void interpolate(const Span< T > src, const Span< int > indices, const Span< float > factors, MutableSpan< T > dst)
void sample_uniform(Span< float > accumulated_segment_lengths, bool include_last_point, MutableSpan< int > r_segment_indices, MutableSpan< float > r_factors)
T safe_divide(const T &a, const T &b)
T distance(const T &a, const T &b)
T interpolate(const T &a, const T &b, const FactorT &t)
T ceil(const T &a)
T round(const T &a)
Vector< bke::GSpanAttributeWriter > dst
i
Definition text_draw.cc:230