Blender  V3.3
subdivide_curves.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include "BKE_attribute_math.hh"
4 #include "BKE_curves.hh"
5 #include "BKE_curves_utils.hh"
6 #include "BKE_geometry_set.hh"
7 
8 #include "BLI_task.hh"
9 
10 #include "GEO_subdivide_curves.hh"
11 
12 namespace blender::geometry {
13 
19 static IndexRange curve_dst_offsets(const IndexRange points, const int curve_index)
20 {
21  return {curve_index + points.start(), points.size() + 1};
22 }
23 
24 static void calculate_result_offsets(const bke::CurvesGeometry &src_curves,
25  const IndexMask selection,
26  const Span<IndexRange> unselected_ranges,
27  const VArray<int> &cuts,
28  const Span<bool> cyclic,
29  MutableSpan<int> dst_curve_offsets,
30  MutableSpan<int> dst_point_offsets)
31 {
32  /* Fill the array with each curve's point count, then accumulate them to the offsets. */
33  bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curve_offsets);
34  threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
35  for (const int curve_i : selection.slice(range)) {
36  const IndexRange src_points = src_curves.points_for_curve(curve_i);
37  const IndexRange src_segments = curve_dst_offsets(src_points, curve_i);
38 
39  MutableSpan<int> point_offsets = dst_point_offsets.slice(src_segments);
40  MutableSpan<int> point_counts = point_offsets.drop_back(1);
41 
42  if (src_points.size() == 1) {
43  point_counts.first() = 1;
44  }
45  else {
46  cuts.materialize_compressed(src_points, point_counts);
47  for (int &count : point_counts) {
48  /* Make sure there at least one cut, and add one for the existing point. */
49  count = std::max(count, 0) + 1;
50  }
51  if (!cyclic[curve_i]) {
52  /* The last point only has a segment to be subdivided if the curve isn't cyclic. */
53  point_counts.last() = 1;
54  }
55  }
56 
57  bke::curves::accumulate_counts_to_offsets(point_offsets);
58  dst_curve_offsets[curve_i] = point_offsets.last();
59  }
60  });
62 }
63 
64 template<typename T>
65 static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> dst)
66 {
67  dst.first() = a;
68  const float step = 1.0f / dst.size();
69  for (const int i : dst.index_range().drop_front(1)) {
70  dst[i] = attribute_math::mix2(i * step, a, b);
71  }
72 }
73 
74 template<typename T>
75 static void subdivide_attribute_linear(const bke::CurvesGeometry &src_curves,
76  const bke::CurvesGeometry &dst_curves,
77  const IndexMask selection,
78  const Span<int> point_offsets,
79  const Span<T> src,
80  MutableSpan<T> dst)
81 {
82  threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) {
83  for (const int curve_i : selection.slice(selection_range)) {
84  const IndexRange src_points = src_curves.points_for_curve(curve_i);
85  const IndexRange src_segments = curve_dst_offsets(src_points, curve_i);
86  const Span<int> offsets = point_offsets.slice(src_segments);
87 
88  const IndexRange dst_points = dst_curves.points_for_curve(curve_i);
89  const Span<T> curve_src = src.slice(src_points);
90  MutableSpan<T> curve_dst = dst.slice(dst_points);
91 
92  threading::parallel_for(curve_src.index_range().drop_back(1), 1024, [&](IndexRange range) {
93  for (const int i : range) {
94  const IndexRange segment_points = bke::offsets_to_range(offsets, i);
95  linear_interpolation(curve_src[i], curve_src[i + 1], curve_dst.slice(segment_points));
96  }
97  });
98 
99  const IndexRange dst_last_segment = dst_points.slice(
100  bke::offsets_to_range(offsets, src_points.size() - 1));
101  linear_interpolation(curve_src.last(), curve_src.first(), dst.slice(dst_last_segment));
102  }
103  });
104 }
105 
106 static void subdivide_attribute_linear(const bke::CurvesGeometry &src_curves,
107  const bke::CurvesGeometry &dst_curves,
108  const IndexMask selection,
109  const Span<int> point_offsets,
110  const GSpan src,
111  GMutableSpan dst)
112 {
113  attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) {
114  using T = decltype(dummy);
115  subdivide_attribute_linear(
116  src_curves, dst_curves, selection, point_offsets, src.typed<T>(), dst.typed<T>());
117  });
118 }
119 
120 template<typename T>
122  const bke::CurvesGeometry &dst_curves,
123  const IndexMask selection,
124  const Span<int> point_offsets,
125  const Span<bool> cyclic,
126  const Span<T> src,
127  MutableSpan<T> dst)
128 {
129  threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) {
130  for (const int curve_i : selection.slice(selection_range)) {
131  const IndexRange src_points = src_curves.points_for_curve(curve_i);
132  const IndexRange src_segments = curve_dst_offsets(src_points, curve_i);
133  const IndexRange dst_points = dst_curves.points_for_curve(curve_i);
134 
135  bke::curves::catmull_rom::interpolate_to_evaluated(src.slice(src_points),
136  cyclic[curve_i],
137  point_offsets.slice(src_segments),
138  dst.slice(dst_points));
139  }
140  });
141 }
142 
144  const bke::CurvesGeometry &dst_curves,
145  const IndexMask selection,
146  const Span<int> point_offsets,
147  const Span<bool> cyclic,
148  const GSpan src,
149  GMutableSpan dst)
150 {
151  attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) {
152  using T = decltype(dummy);
153  subdivide_attribute_catmull_rom(
154  src_curves, dst_curves, selection, point_offsets, cyclic, src.typed<T>(), dst.typed<T>());
155  });
156 }
157 
158 static void subdivide_bezier_segment(const float3 &position_prev,
159  const float3 &handle_prev,
160  const float3 &handle_next,
161  const float3 &position_next,
162  const HandleType type_prev,
163  const HandleType type_next,
164  const IndexRange segment_points,
165  MutableSpan<float3> dst_positions,
166  MutableSpan<float3> dst_handles_l,
167  MutableSpan<float3> dst_handles_r,
168  MutableSpan<int8_t> dst_types_l,
169  MutableSpan<int8_t> dst_types_r,
170  const bool is_last_cyclic_segment)
171 {
172  auto fill_segment_handle_types = [&](const HandleType type) {
173  /* Also change the left handle of the control point following the segment's points. And don't
174  * change the left handle of the first point, since that is part of the previous segment. */
175  dst_types_l.slice(segment_points.shift(1)).fill(type);
176  dst_types_r.slice(segment_points).fill(type);
177  };
178 
179  if (bke::curves::bezier::segment_is_vector(type_prev, type_next)) {
180  linear_interpolation(position_prev, position_next, dst_positions.slice(segment_points));
181  fill_segment_handle_types(BEZIER_HANDLE_VECTOR);
182  }
183  else {
184  /* The first point in the segment is always copied. */
185  dst_positions[segment_points.first()] = position_prev;
186 
187  /* Non-vector segments in the result curve are given free handles. This could possibly be
188  * improved with another pass that sets handles to aligned where possible, but currently that
189  * does not provide much benefit for the increased complexity. */
190  fill_segment_handle_types(BEZIER_HANDLE_FREE);
191 
192  /* In order to generate a Bezier curve with the same shape as the input curve, apply the
193  * De Casteljau algorithm iteratively for the provided number of cuts, constantly updating the
194  * previous result point's right handle and the left handle at the end of the segment. */
195  float3 segment_start = position_prev;
196  float3 segment_handle_prev = handle_prev;
197  float3 segment_handle_next = handle_next;
198  const float3 segment_end = position_next;
199 
200  for (const int i : IndexRange(segment_points.size() - 1)) {
201  const float parameter = 1.0f / (segment_points.size() - i);
202  const int point_i = segment_points[i];
204  segment_start, segment_handle_prev, segment_handle_next, segment_end, parameter);
205 
206  /* Copy relevant temporary data to the result. */
207  dst_handles_r[point_i] = insert.handle_prev;
208  dst_handles_l[point_i + 1] = insert.left_handle;
209  dst_positions[point_i + 1] = insert.position;
210 
211  /* Update the segment to prepare it for the next subdivision. */
212  segment_start = insert.position;
213  segment_handle_prev = insert.right_handle;
214  segment_handle_next = insert.handle_next;
215  }
216 
217  /* Copy the handles for the last segment from the working variables. */
218  const int i_segment_last = is_last_cyclic_segment ? 0 : segment_points.one_after_last();
219  dst_handles_r[segment_points.last()] = segment_handle_prev;
220  dst_handles_l[i_segment_last] = segment_handle_next;
221  }
222 }
223 
224 static void subdivide_bezier_positions(const Span<float3> src_positions,
225  const Span<int8_t> src_types_l,
226  const Span<int8_t> src_types_r,
227  const Span<float3> src_handles_l,
228  const Span<float3> src_handles_r,
229  const Span<int> evaluated_offsets,
230  const bool cyclic,
231  MutableSpan<float3> dst_positions,
232  MutableSpan<int8_t> dst_types_l,
233  MutableSpan<int8_t> dst_types_r,
234  MutableSpan<float3> dst_handles_l,
235  MutableSpan<float3> dst_handles_r)
236 {
237  threading::parallel_for(src_positions.index_range().drop_back(1), 512, [&](IndexRange range) {
238  for (const int segment_i : range) {
239  const IndexRange segment = bke::offsets_to_range(evaluated_offsets, segment_i);
240  subdivide_bezier_segment(src_positions[segment_i],
241  src_handles_r[segment_i],
242  src_handles_l[segment_i + 1],
243  src_positions[segment_i + 1],
244  HandleType(src_types_r[segment_i]),
245  HandleType(src_types_l[segment_i + 1]),
246  segment,
247  dst_positions,
248  dst_handles_l,
249  dst_handles_r,
250  dst_types_l,
251  dst_types_r,
252  false);
253  }
254  });
255 
256  if (cyclic) {
257  const int last_index = src_positions.index_range().last();
258  const IndexRange segment = bke::offsets_to_range(evaluated_offsets, last_index);
259  const HandleType type_prev = HandleType(src_types_r.last());
260  const HandleType type_next = HandleType(src_types_l.first());
261  subdivide_bezier_segment(src_positions.last(),
262  src_handles_r.last(),
263  src_handles_l.first(),
264  src_positions.first(),
265  type_prev,
266  type_next,
267  segment,
268  dst_positions,
269  dst_handles_l,
270  dst_handles_r,
271  dst_types_l,
272  dst_types_r,
273  true);
274 
275  if (bke::curves::bezier::segment_is_vector(type_prev, type_next)) {
276  dst_types_l.first() = BEZIER_HANDLE_VECTOR;
277  dst_types_r.last() = BEZIER_HANDLE_VECTOR;
278  }
279  else {
280  dst_types_l.first() = BEZIER_HANDLE_FREE;
281  dst_types_r.last() = BEZIER_HANDLE_FREE;
282  }
283  }
284  else {
285  dst_positions.last() = src_positions.last();
286  dst_types_l.first() = src_types_l.first();
287  dst_types_r.last() = src_types_r.last();
288  dst_handles_l.first() = src_handles_l.first();
289  dst_handles_r.last() = src_handles_r.last();
290  }
291 
292  /* TODO: It would be possible to avoid calling this for all segments besides vector segments. */
294  cyclic, dst_types_l, dst_types_r, dst_positions, dst_handles_l, dst_handles_r);
295 }
296 
298  const IndexMask selection,
299  const VArray<int> &cuts)
300 {
301  const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert(
302  src_curves.curves_range());
303 
304  /* Cyclic is accessed a lot, it's probably worth it to make sure it's a span. */
305  const VArraySpan<bool> cyclic{src_curves.cyclic()};
306 
308 
309  /* For each point, this contains the point offset in the corresponding result curve,
310  * starting at zero. For example for two curves with four points each, the values might
311  * look like this:
312  *
313  * | | Curve 0 | Curve 1 |
314  * | ------------------- |---|---|---|---|---|---|---|---|---|----|
315  * | Cuts | 0 | 3 | 0 | 0 | - | 2 | 0 | 0 | 4 | - |
316  * | New Point Count | 1 | 4 | 1 | 1 | - | 3 | 1 | 1 | 5 | - |
317  * | Accumulated Offsets | 0 | 1 | 5 | 6 | 7 | 0 | 3 | 4 | 5 | 10 |
318  *
319  * Storing the leading zero is unnecessary but makes the array a bit simpler to use by avoiding
320  * a check for the first segment, and because some existing utilities also use leading zeros. */
321  Array<int> dst_point_offsets(src_curves.points_num() + src_curves.curves_num());
322 #ifdef DEBUG
323  dst_point_offsets.fill(-1);
324 #endif
325  calculate_result_offsets(src_curves,
326  selection,
327  unselected_ranges,
328  cuts,
329  cyclic,
330  dst_curves.offsets_for_write(),
331  dst_point_offsets);
332  const Span<int> point_offsets = dst_point_offsets.as_span();
333 
334  dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num());
335 
336  const bke::AttributeAccessor src_attributes = src_curves.attributes();
337  bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
338 
339  auto subdivide_catmull_rom = [&](IndexMask selection) {
341  src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) {
343  dst_curves,
344  selection,
345  point_offsets,
346  cyclic,
347  attribute.src,
348  attribute.dst.span);
349  attribute.dst.finish();
350  }
351  };
352 
353  auto subdivide_poly = [&](IndexMask selection) {
355  src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) {
357  src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span);
358  attribute.dst.finish();
359  }
360  };
361 
362  auto subdivide_bezier = [&](IndexMask selection) {
363  const Span<float3> src_positions = src_curves.positions();
364  const VArraySpan<int8_t> src_types_l{src_curves.handle_types_left()};
365  const VArraySpan<int8_t> src_types_r{src_curves.handle_types_right()};
366  const Span<float3> src_handles_l = src_curves.handle_positions_left();
367  const Span<float3> src_handles_r = src_curves.handle_positions_right();
368 
369  MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
370  MutableSpan<int8_t> dst_types_l = dst_curves.handle_types_left_for_write();
371  MutableSpan<int8_t> dst_types_r = dst_curves.handle_types_right_for_write();
372  MutableSpan<float3> dst_handles_l = dst_curves.handle_positions_left_for_write();
373  MutableSpan<float3> dst_handles_r = dst_curves.handle_positions_right_for_write();
374 
375  threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
376  for (const int curve_i : selection.slice(range)) {
377  const IndexRange src_points = src_curves.points_for_curve(curve_i);
378  const IndexRange src_segments = curve_dst_offsets(src_points, curve_i);
379 
380  const IndexRange dst_points = dst_curves.points_for_curve(curve_i);
381  subdivide_bezier_positions(src_positions.slice(src_points),
382  src_types_l.slice(src_points),
383  src_types_r.slice(src_points),
384  src_handles_l.slice(src_points),
385  src_handles_r.slice(src_points),
386  point_offsets.slice(src_segments),
387  cyclic[curve_i],
388  dst_positions.slice(dst_points),
389  dst_types_l.slice(dst_points),
390  dst_types_r.slice(dst_points),
391  dst_handles_l.slice(dst_points),
392  dst_handles_r.slice(dst_points));
393  }
394  });
395 
396  for (auto &attribute : bke::retrieve_attributes_for_transfer(src_attributes,
397  dst_attributes,
399  {"position",
400  "handle_type_left",
401  "handle_type_right",
402  "handle_right",
403  "handle_left"})) {
405  src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span);
406  attribute.dst.finish();
407  }
408  };
409 
410  /* NURBS curves are just treated as poly curves. NURBS subdivision that maintains
411  * their shape may be possible, but probably wouldn't work with the "cuts" input. */
412  auto subdivide_nurbs = subdivide_poly;
413 
414  bke::curves::foreach_curve_by_type(src_curves.curve_types(),
415  src_curves.curve_type_counts(),
416  selection,
417  subdivide_catmull_rom,
418  subdivide_poly,
419  subdivide_bezier,
420  subdivide_nurbs);
421 
422  if (!unselected_ranges.is_empty()) {
424  src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) {
426  src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span);
427  attribute.dst.finish();
428  }
429  }
430 
431  return dst_curves;
432 }
433 
434 } // namespace blender::geometry
@ ATTR_DOMAIN_MASK_POINT
Definition: BKE_attribute.h:37
Low-level operations for curves.
Low-level operations for curves.
HandleType
@ BEZIER_HANDLE_FREE
@ BEZIER_HANDLE_VECTOR
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum type
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 Sky Generate a procedural sky texture Noise Generate fractal Perlin noise Wave Generate procedural bands or rings with noise 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:231
void fill(const T &value) const
Definition: BLI_array.hh:260
const CPPType & type() const
Vector< IndexRange > extract_ranges_invert(const IndexRange full_range, Vector< int64_t > *r_skip_amounts=nullptr) const
Definition: index_mask.cc:100
IndexRange index_range() const
constexpr int64_t first() const
constexpr int64_t one_after_last() const
constexpr IndexRange shift(int64_t n) const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr int64_t start() const
constexpr IndexRange drop_front(int64_t n) const
constexpr int64_t size() const
Definition: BLI_span.hh:511
constexpr void fill(const T &value)
Definition: BLI_span.hh:527
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition: BLI_span.hh:581
constexpr IndexRange index_range() const
Definition: BLI_span.hh:661
constexpr T & first() const
Definition: BLI_span.hh:670
constexpr const T & last(const int64_t n=0) const
Definition: BLI_span.hh:313
constexpr IndexRange index_range() const
Definition: BLI_span.hh:401
VArray< int8_t > handle_types_left() const
MutableSpan< float3 > positions_for_write()
MutableSpan< int8_t > handle_types_right_for_write()
VArray< int8_t > handle_types_right() const
IndexRange curves_range() const
Definition: BKE_curves.hh:795
MutableSpan< float3 > handle_positions_left_for_write()
MutableAttributeAccessor attributes_for_write()
MutableSpan< float3 > handle_positions_right_for_write()
Span< float3 > handle_positions_left() const
Span< float3 > positions() const
void resize(int points_num, int curves_num)
Span< float3 > handle_positions_right() const
AttributeAccessor attributes() const
MutableSpan< int > offsets_for_write()
VArray< bool > cyclic() const
MutableSpan< int8_t > handle_types_left_for_write()
SyclQueue void void * src
#define T
Segment< FEdge *, Vec3r > segment
static unsigned a[3]
Definition: RandGen.cpp:78
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
T mix2(float factor, const T &a, const T &b)
Insertion insert(const float3 &point_prev, const float3 &handle_prev, const float3 &handle_next, const float3 &point_next, float parameter)
Definition: curve_bezier.cc:61
bool segment_is_vector(const HandleType left, const HandleType right)
Definition: BKE_curves.hh:912
void calculate_auto_handles(bool cyclic, Span< int8_t > types_left, Span< int8_t > types_right, Span< float3 > positions, MutableSpan< float3 > positions_left, MutableSpan< float3 > positions_right)
void fill_curve_counts(const bke::CurvesGeometry &curves, Span< IndexRange > curve_ranges, MutableSpan< int > counts)
Definition: curves_utils.cc:13
void accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
Definition: curves_utils.cc:28
bke::CurvesGeometry copy_only_curve_domain(const bke::CurvesGeometry &src_curves)
Definition: curves_utils.cc:87
void copy_point_data(const CurvesGeometry &src_curves, const CurvesGeometry &dst_curves, Span< IndexRange > curve_ranges, GSpan src, GMutableSpan dst)
Definition: curves_utils.cc:40
void foreach_curve_by_type(const VArray< int8_t > &types, const std::array< int, CURVE_TYPES_NUM > &type_counts, IndexMask selection, FunctionRef< void(IndexMask)> catmull_rom_fn, FunctionRef< void(IndexMask)> poly_fn, FunctionRef< void(IndexMask)> bezier_fn, FunctionRef< void(IndexMask)> nurbs_fn)
Vector< AttributeTransferData > retrieve_attributes_for_transfer(const bke::AttributeAccessor src_attributes, bke::MutableAttributeAccessor dst_attributes, eAttrDomainMask domain_mask, const Set< std::string > &skip={})
constexpr IndexRange offsets_to_range(Span< T > offsets, int64_t index)
Definition: BKE_curves.hh:29
static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span< IndexRange > unselected_ranges, const VArray< int > &cuts, const Span< bool > cyclic, MutableSpan< int > dst_curve_offsets, MutableSpan< int > dst_point_offsets)
static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span< IndexRange > unselected_ranges, const VArray< float > &radii, const VArray< int > &counts, const Span< bool > cyclic, MutableSpan< int > dst_curve_offsets, MutableSpan< int > dst_point_offsets)
static void subdivide_attribute_catmull_rom(const bke::CurvesGeometry &src_curves, const bke::CurvesGeometry &dst_curves, const IndexMask selection, const Span< int > point_offsets, const Span< bool > cyclic, const GSpan src, GMutableSpan dst)
static IndexRange curve_dst_offsets(const IndexRange points, const int curve_index)
static void subdivide_attribute_linear(const bke::CurvesGeometry &src_curves, const bke::CurvesGeometry &dst_curves, const IndexMask selection, const Span< int > point_offsets, const GSpan src, GMutableSpan dst)
static void linear_interpolation(const T &a, const T &b, MutableSpan< T > dst)
static void subdivide_bezier_segment(const float3 &position_prev, const float3 &handle_prev, const float3 &handle_next, const float3 &position_next, const HandleType type_prev, const HandleType type_next, const IndexRange segment_points, MutableSpan< float3 > dst_positions, MutableSpan< float3 > dst_handles_l, MutableSpan< float3 > dst_handles_r, MutableSpan< int8_t > dst_types_l, MutableSpan< int8_t > dst_types_r, const bool is_last_cyclic_segment)
static void subdivide_bezier_positions(const Span< float3 > src_positions, const Span< int8_t > src_types_l, const Span< int8_t > src_types_r, const Span< float3 > src_handles_l, const Span< float3 > src_handles_r, const Span< int > evaluated_offsets, const bool cyclic, MutableSpan< float3 > dst_positions, MutableSpan< int8_t > dst_types_l, MutableSpan< int8_t > dst_types_r, MutableSpan< float3 > dst_handles_l, MutableSpan< float3 > dst_handles_r)
bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, IndexMask selection, const VArray< int > &cuts)
void parallel_for(IndexRange range, int64_t grain_size, const Function &function)
Definition: BLI_task.hh:51
static const pxr::TfToken b("b", pxr::TfToken::Immortal)