Blender V4.3
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#include "BKE_curves.hh"
7
8#include "BLI_array_utils.hh"
9#include "BLI_assert.h"
11#include "BLI_math_vector.hh"
12#include "BLI_offset_indices.hh"
13#include "BLI_task.hh"
14
16
19
20namespace blender::geometry {
21
22using bke::CurvesGeometry;
23
28static bool interpolate_attribute_to_curves(const StringRef attribute_id,
29 const std::array<int, CURVE_TYPES_NUM> &type_counts)
30{
31 if (bke::attribute_name_is_anonymous(attribute_id)) {
32 return true;
33 }
34 if (ELEM(attribute_id, "handle_type_left", "handle_type_right", "handle_left", "handle_right")) {
35 return type_counts[CURVE_TYPE_BEZIER] != 0;
36 }
37 if (ELEM(attribute_id, "nurbs_weight")) {
38 return type_counts[CURVE_TYPE_NURBS] != 0;
39 }
40 return true;
41}
42
46static bool interpolate_attribute_to_poly_curve(const StringRef attribute_id)
47{
48 static const Set<StringRef> no_interpolation{{
49 "handle_type_left",
50 "handle_type_right",
51 "handle_right",
52 "handle_left",
53 "nurbs_weight",
54 }};
55 return !no_interpolation.contains(attribute_id);
56}
57
64
69 const CurvesGeometry &src_from_curves,
70 const CurvesGeometry &src_to_curves,
71 const bke::AttrDomain domain,
72 CurvesGeometry &dst_curves)
73{
75
76 const bke::AttributeAccessor src_from_attributes = src_from_curves.attributes();
77 const bke::AttributeAccessor src_to_attributes = src_to_curves.attributes();
78 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
79 for (const int i : ids.index_range()) {
80 eCustomDataType data_type;
81
82 const GVArray src_from_attribute = *src_from_attributes.lookup(ids[i], domain);
83 if (src_from_attribute) {
84 data_type = bke::cpp_type_to_custom_data_type(src_from_attribute.type());
85
86 const GVArray src_to_attribute = *src_to_attributes.lookup(ids[i], domain, data_type);
87
88 result.src_from.append(src_from_attribute);
89 result.src_to.append(src_to_attribute ? src_to_attribute : GVArraySpan{});
90 }
91 else {
92 const GVArray src_to_attribute = *src_to_attributes.lookup(ids[i], domain);
93 /* Attribute should exist on at least one of the geometries. */
94 BLI_assert(src_to_attribute);
95
96 data_type = bke::cpp_type_to_custom_data_type(src_to_attribute.type());
97
98 result.src_from.append(GVArraySpan{});
99 result.src_to.append(src_to_attribute);
100 }
101
103 ids[i], domain, data_type);
104 result.dst.append(std::move(dst_attribute));
105 }
106
107 return result;
108}
109
114 const CurvesGeometry &from_curves, const CurvesGeometry &to_curves, CurvesGeometry &dst_curves)
115{
117 auto add_attribute = [&](const bke::AttributeIter &iter) {
118 if (iter.domain != bke::AttrDomain::Point) {
119 return;
120 }
121 if (iter.data_type == CD_PROP_STRING) {
122 return;
123 }
124 if (!interpolate_attribute_to_curves(iter.name, dst_curves.curve_type_counts())) {
125 return;
126 }
127 if (!interpolate_attribute_to_poly_curve(iter.name)) {
128 return;
129 }
130 /* Position is handled differently since it has non-generic interpolation for Bezier
131 * curves and because the evaluated positions are cached for each evaluated point. */
132 if (iter.name == "position") {
133 return;
134 }
135
136 ids.add(iter.name);
137 };
138
139 from_curves.attributes().foreach_attribute(add_attribute);
140 to_curves.attributes().foreach_attribute(add_attribute);
141
142 return retrieve_attribute_spans(ids, from_curves, to_curves, bke::AttrDomain::Point, dst_curves);
143}
144
149 const CurvesGeometry &from_curves, const CurvesGeometry &to_curves, CurvesGeometry &dst_curves)
150{
152 auto add_attribute = [&](const bke::AttributeIter &iter) {
153 if (iter.domain != bke::AttrDomain::Curve) {
154 return;
155 }
156 if (iter.data_type == CD_PROP_STRING) {
157 return;
158 }
159 if (bke::attribute_name_is_anonymous(iter.name)) {
160 return;
161 }
162 /* Interpolation tool always outputs poly curves. */
163 if (iter.name == "curve_type") {
164 return;
165 }
166
167 ids.add(iter.name);
168 };
169
170 from_curves.attributes().foreach_attribute(add_attribute);
171 to_curves.attributes().foreach_attribute(add_attribute);
172
173 return retrieve_attribute_spans(ids, from_curves, to_curves, bke::AttrDomain::Curve, dst_curves);
174}
175
176/* Resample a span of attribute values from source curves to a destination buffer. */
177static void sample_curve_attribute(const bke::CurvesGeometry &src_curves,
178 const Span<int> src_curve_indices,
179 const OffsetIndices<int> dst_points_by_curve,
180 const GSpan src_data,
181 const IndexMask &dst_curve_mask,
182 const Span<int> dst_sample_indices,
183 const Span<float> dst_sample_factors,
184 GMutableSpan dst_data)
185{
186 const CPPType &type = src_data.type();
187 BLI_assert(dst_data.type() == type);
188
189 const OffsetIndices<int> src_points_by_curve = src_curves.points_by_curve();
190 const OffsetIndices<int> src_evaluated_points_by_curve = src_curves.evaluated_points_by_curve();
191 const VArray<int8_t> curve_types = src_curves.curve_types();
192
193#ifndef NDEBUG
194 const int dst_points_num = dst_data.size();
195 BLI_assert(dst_sample_indices.size() == dst_points_num);
196 BLI_assert(dst_sample_factors.size() == dst_points_num);
197#endif
198
199 bke::attribute_math::convert_to_static_type(type, [&](auto dummy) {
200 using T = decltype(dummy);
201 Span<T> src = src_data.typed<T>();
202 MutableSpan<T> dst = dst_data.typed<T>();
203
204 Vector<T> evaluated_data;
205 dst_curve_mask.foreach_index([&](const int i_dst_curve, const int pos) {
206 const int i_src_curve = src_curve_indices[pos];
207 const IndexRange src_points = src_points_by_curve[i_src_curve];
208 const IndexRange dst_points = dst_points_by_curve[i_dst_curve];
209
210 if (curve_types[i_src_curve] == CURVE_TYPE_POLY) {
212 dst_sample_indices.slice(dst_points),
213 dst_sample_factors.slice(dst_points),
214 dst.slice(dst_points));
215 }
216 else {
217 const IndexRange src_evaluated_points = src_evaluated_points_by_curve[i_src_curve];
218 evaluated_data.reinitialize(src_evaluated_points.size());
219 src_curves.interpolate_to_evaluated(
220 i_src_curve, src.slice(src_points), evaluated_data.as_mutable_span());
221 length_parameterize::interpolate(evaluated_data.as_span(),
222 dst_sample_indices.slice(dst_points),
223 dst_sample_factors.slice(dst_points),
224 dst.slice(dst_points));
225 }
226 });
227 });
228}
229
230template<typename T>
231static void mix_arrays(const Span<T> from,
232 const Span<T> to,
233 const float mix_factor,
234 const MutableSpan<T> dst)
235{
236 for (const int i : dst.index_range()) {
237 dst[i] = math::interpolate(from[i], to[i], mix_factor);
238 }
239}
240
241static void mix_arrays(const GSpan src_from,
242 const GSpan src_to,
243 const float mix_factor,
244 const IndexMask &selection,
245 const GMutableSpan dst)
246{
248 using T = decltype(dummy);
249 const Span<T> from = src_from.typed<T>();
250 const Span<T> to = src_to.typed<T>();
251 const MutableSpan<T> dst_typed = dst.typed<T>();
252 selection.foreach_index(GrainSize(512), [&](const int curve) {
253 dst_typed[curve] = math::interpolate(from[curve], to[curve], mix_factor);
254 });
255 });
256}
257
258static void mix_arrays(const GSpan src_from,
259 const GSpan src_to,
260 const float mix_factor,
261 const IndexMask &group_selection,
262 const OffsetIndices<int> groups,
263 const GMutableSpan dst)
264{
265 group_selection.foreach_index(GrainSize(32), [&](const int curve) {
266 const IndexRange range = groups[curve];
267 bke::attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) {
268 using T = decltype(dummy);
269 const Span<T> from = src_from.typed<T>();
270 const Span<T> to = src_to.typed<T>();
271 const MutableSpan<T> dst_typed = dst.typed<T>();
272 mix_arrays(from.slice(range), to.slice(range), mix_factor, dst_typed.slice(range));
273 });
274 });
275}
276
277void interpolate_curves(const CurvesGeometry &from_curves,
278 const CurvesGeometry &to_curves,
279 const Span<int> from_curve_indices,
280 const Span<int> to_curve_indices,
281 const IndexMask &dst_curve_mask,
282 const Span<bool> dst_curve_flip_direction,
283 const float mix_factor,
284 CurvesGeometry &dst_curves)
285{
286 BLI_assert(from_curve_indices.size() == dst_curve_mask.size());
287 BLI_assert(to_curve_indices.size() == dst_curve_mask.size());
288
289 if (from_curves.curves_num() == 0 || to_curves.curves_num() == 0) {
290 return;
291 }
292
293 const VArray<bool> from_curves_cyclic = from_curves.cyclic();
294 const VArray<bool> to_curves_cyclic = to_curves.cyclic();
295 const Span<float3> from_evaluated_positions = from_curves.evaluated_positions();
296 const Span<float3> to_evaluated_positions = to_curves.evaluated_positions();
297
298 /* All resampled curves are poly curves. */
299 dst_curves.fill_curve_types(dst_curve_mask, CURVE_TYPE_POLY);
300
301 MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
302
304 from_curves, to_curves, dst_curves);
306 from_curves, to_curves, dst_curves);
307
308 from_curves.ensure_evaluated_lengths();
309 to_curves.ensure_evaluated_lengths();
310
311 /* Sampling arbitrary attributes works by first interpolating them to the curve's standard
312 * "evaluated points" and then interpolating that result with the uniform samples. This is
313 * potentially wasteful when down-sampling a curve to many fewer points. There are two possible
314 * solutions: only sample the necessary points for interpolation, or first sample curve
315 * parameter/segment indices and evaluate the curve directly. */
316 Array<int> from_sample_indices(dst_curves.points_num());
317 Array<int> to_sample_indices(dst_curves.points_num());
318 Array<float> from_sample_factors(dst_curves.points_num());
319 Array<float> to_sample_factors(dst_curves.points_num());
320
321 const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
322
323 /* Gather uniform samples based on the accumulated lengths of the original curve. */
324 dst_curve_mask.foreach_index(GrainSize(32), [&](const int i_dst_curve, const int pos) {
325 const int i_from_curve = from_curve_indices[pos];
326 const int i_to_curve = to_curve_indices[pos];
327 const IndexRange dst_points = dst_points_by_curve[i_dst_curve];
328 const Span<float> from_lengths = from_curves.evaluated_lengths_for_curve(
329 i_from_curve, from_curves_cyclic[i_from_curve]);
330 const Span<float> to_lengths = to_curves.evaluated_lengths_for_curve(
331 i_to_curve, to_curves_cyclic[i_to_curve]);
332
333 if (from_lengths.is_empty()) {
334 /* Handle curves with only one evaluated point. */
335 from_sample_indices.as_mutable_span().slice(dst_points).fill(0);
336 from_sample_factors.as_mutable_span().slice(dst_points).fill(0.0f);
337 }
338 else {
339 length_parameterize::sample_uniform(from_lengths,
340 !from_curves_cyclic[i_from_curve],
341 from_sample_indices.as_mutable_span().slice(dst_points),
342 from_sample_factors.as_mutable_span().slice(dst_points));
343 }
344 if (to_lengths.is_empty()) {
345 /* Handle curves with only one evaluated point. */
346 to_sample_indices.as_mutable_span().slice(dst_points).fill(0);
347 to_sample_factors.as_mutable_span().slice(dst_points).fill(0.0f);
348 }
349 else {
350 if (dst_curve_flip_direction[i_dst_curve]) {
351 length_parameterize::sample_uniform_reverse(
352 to_lengths,
353 !to_curves_cyclic[i_to_curve],
354 to_sample_indices.as_mutable_span().slice(dst_points),
355 to_sample_factors.as_mutable_span().slice(dst_points));
356 }
357 else {
358 length_parameterize::sample_uniform(to_lengths,
359 !to_curves_cyclic[i_to_curve],
360 to_sample_indices.as_mutable_span().slice(dst_points),
361 to_sample_factors.as_mutable_span().slice(dst_points));
362 }
363 }
364 });
365
366 /* For every attribute, evaluate attributes from every curve in the range in the original
367 * curve's "evaluated points", then use linear interpolation to sample to the result. */
368 for (const int i_attribute : point_attributes.dst.index_range()) {
369 /* Attributes that exist already on another domain can not be written to. */
370 if (!point_attributes.dst[i_attribute]) {
371 continue;
372 }
373
374 const GSpan src_from = point_attributes.src_from[i_attribute];
375 const GSpan src_to = point_attributes.src_to[i_attribute];
376 GMutableSpan dst = point_attributes.dst[i_attribute].span;
377
378 /* Mix factors depend on which of the from/to curves geometries has attribute data. If
379 * only one geometry has attribute data it gets the full mix weight. */
380 if (!src_from.is_empty() && !src_to.is_empty()) {
381 GArray<> from_samples(dst.type(), dst.size());
382 GArray<> to_samples(dst.type(), dst.size());
383 sample_curve_attribute(from_curves,
384 from_curve_indices,
385 dst_points_by_curve,
386 src_from,
387 dst_curve_mask,
388 from_sample_indices,
389 from_sample_factors,
390 from_samples);
391 sample_curve_attribute(to_curves,
392 to_curve_indices,
393 dst_points_by_curve,
394 src_to,
395 dst_curve_mask,
396 to_sample_indices,
397 to_sample_factors,
398 to_samples);
399 mix_arrays(from_samples, to_samples, mix_factor, dst_curve_mask, dst_points_by_curve, dst);
400 }
401 else if (!src_from.is_empty()) {
402 sample_curve_attribute(from_curves,
403 from_curve_indices,
404 dst_points_by_curve,
405 src_from,
406 dst_curve_mask,
407 from_sample_indices,
408 from_sample_factors,
409 dst);
410 }
411 else if (!src_to.is_empty()) {
412 sample_curve_attribute(to_curves,
413 to_curve_indices,
414 dst_points_by_curve,
415 src_to,
416 dst_curve_mask,
417 to_sample_indices,
418 to_sample_factors,
419 dst);
420 }
421 }
422
423 {
424 Array<float3> from_samples(dst_positions.size());
425 Array<float3> to_samples(dst_positions.size());
426
427 /* Interpolate the evaluated positions to the resampled curves. */
428 sample_curve_attribute(from_curves,
429 from_curve_indices,
430 dst_points_by_curve,
431 from_evaluated_positions,
432 dst_curve_mask,
433 from_sample_indices,
434 from_sample_factors,
435 from_samples.as_mutable_span());
436 sample_curve_attribute(to_curves,
437 to_curve_indices,
438 dst_points_by_curve,
439 to_evaluated_positions,
440 dst_curve_mask,
441 to_sample_indices,
442 to_sample_factors,
443 to_samples.as_mutable_span());
444
445 mix_arrays(from_samples.as_span(),
446 to_samples.as_span(),
447 mix_factor,
448 dst_curve_mask,
449 dst_points_by_curve,
450 dst_positions);
451 }
452
453 for (const int i_attribute : curve_attributes.dst.index_range()) {
454 /* Attributes that exist already on another domain can not be written to. */
455 if (!curve_attributes.dst[i_attribute]) {
456 continue;
457 }
458
459 const GSpan src_from = curve_attributes.src_from[i_attribute];
460 const GSpan src_to = curve_attributes.src_to[i_attribute];
461 GMutableSpan dst = curve_attributes.dst[i_attribute].span;
462
463 /* Only mix "safe" attribute types for now. Other types (int, bool, etc.) are just copied from
464 * the first curve of each pair. */
465 const bool can_mix_attribute = ELEM(bke::cpp_type_to_custom_data_type(dst.type()),
469 if (can_mix_attribute && !src_from.is_empty() && !src_to.is_empty()) {
470 GArray<> from_samples(dst.type(), dst.size());
471 GArray<> to_samples(dst.type(), dst.size());
472 array_utils::copy(GVArray::ForSpan(src_from), dst_curve_mask, from_samples);
473 array_utils::copy(GVArray::ForSpan(src_to), dst_curve_mask, to_samples);
474 mix_arrays(from_samples, to_samples, mix_factor, dst_curve_mask, dst);
475 }
476 else if (!src_from.is_empty()) {
477 array_utils::copy(GVArray::ForSpan(src_from), dst_curve_mask, dst);
478 }
479 else if (!src_to.is_empty()) {
480 array_utils::copy(GVArray::ForSpan(src_to), dst_curve_mask, dst);
481 }
482 }
483
484 for (bke::GSpanAttributeWriter &attribute : point_attributes.dst) {
485 attribute.finish();
486 }
487 for (bke::GSpanAttributeWriter &attribute : curve_attributes.dst) {
488 attribute.finish();
489 }
490}
491
492} // namespace blender::geometry
Low-level operations for curves.
#define BLI_assert(a)
Definition BLI_assert.h:50
#define ELEM(...)
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_NURBS
@ CURVE_TYPE_POLY
@ CD_PROP_FLOAT
@ CD_PROP_FLOAT3
@ CD_PROP_FLOAT2
@ CD_PROP_STRING
const CPPType & type() const
MutableSpan< T > typed() const
Span< T > typed() const
const CPPType & type() const
constexpr int64_t size() const
constexpr IndexRange index_range() const
Definition BLI_span.hh:671
bool contains(const Key &key) const
Definition BLI_set.hh:291
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
constexpr bool is_empty() const
Definition BLI_span.hh:261
bool add(const Key &key)
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
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
OffsetIndices< int > evaluated_points_by_curve() const
void fill_curve_types(CurveType type)
Span< float3 > evaluated_positions() const
VArray< int8_t > curve_types() const
VArray< bool > cyclic() const
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type)
void foreach_index(Fn &&fn) const
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(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, const float mix_factor, bke::CurvesGeometry &dst_curves)
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 bool interpolate_attribute_to_poly_curve(const StringRef attribute_id)
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)
void interpolate(const Span< T > src, const Span< int > indices, const Span< float > factors, MutableSpan< T > dst)
T interpolate(const T &a, const T &b, const FactorT &t)
Vector< bke::GSpanAttributeWriter > dst