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];
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 {
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]) {
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 {
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
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a or normal between and object coordinate space Combine Create a color from its and value channels Color Retrieve a color attribute
Span< T > as_span() const
Definition BLI_array.hh:232
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
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 int64_t size() const
Definition BLI_span.hh:494
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
#define T
void copy(const GVArray &src, GMutableSpan dst, int64_t grain_size=4096)
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 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 interpolate(const T &a, const T &b, const FactorT &t)
GPU_SHADER_INTERFACE_INFO(overlay_edit_curve_handle_iface, "vert").flat(Type pos vertex_in(1, Type::UINT, "data") .vertex_out(overlay_edit_curve_handle_iface) .geometry_layout(PrimitiveIn Frequency::GEOMETRY storage_buf(1, Qualifier::READ, "uint", "data[]", Frequency::GEOMETRY) .push_constant(Type Frequency::GEOMETRY selection[]
Vector< bke::GSpanAttributeWriter > dst