Blender V5.0
subdivide_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#include "BKE_curves_utils.hh"
8#include "BKE_deform.hh"
9
10#include "BLI_array_utils.hh"
11#include "BLI_task.hh"
12
14
15namespace blender::geometry {
16
17static void calculate_result_offsets(const bke::CurvesGeometry &src_curves,
18 const IndexMask &selection,
19 const IndexMask &unselected,
20 const VArray<int> &cuts,
21 const Span<bool> cyclic,
22 MutableSpan<int> dst_curve_offsets,
23 MutableSpan<int> dst_point_offsets)
24{
25 /* Fill the array with each curve's point count, then accumulate them to the offsets. */
26 const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
27 offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_curve_offsets);
28 selection.foreach_index(GrainSize(1024), [&](const int curve_i) {
29 const IndexRange src_points = src_points_by_curve[curve_i];
30 const IndexRange src_segments = bke::curves::per_curve_point_offsets_range(src_points,
31 curve_i);
32
33 MutableSpan<int> point_offsets = dst_point_offsets.slice(src_segments);
34 MutableSpan<int> point_counts = point_offsets.drop_back(1);
35
36 if (src_points.size() == 1) {
37 point_counts.first() = 1;
38 }
39 else {
40 cuts.materialize_compressed(src_points, point_counts);
41 for (int &count : point_counts) {
42 /* Make sure there at least one cut, and add one for the existing point. */
43 count = std::max(count, 0) + 1;
44 }
45 if (!cyclic[curve_i]) {
46 /* The last point only has a segment to be subdivided if the curve isn't cyclic. */
47 point_counts.last() = 1;
48 }
49 }
50
52 dst_curve_offsets[curve_i] = point_offsets.last();
53 });
55}
56
57template<typename T>
58static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> dst)
59{
60 dst.first() = a;
61 const float step = 1.0f / dst.size();
62 for (const int i : dst.index_range().drop_front(1)) {
63 dst[i] = bke::attribute_math::mix2(i * step, a, b);
64 }
65}
66
67template<typename T>
68static void subdivide_attribute_linear(const OffsetIndices<int> src_points_by_curve,
69 const OffsetIndices<int> dst_points_by_curve,
70 const IndexMask &selection,
71 const Span<int> all_point_offsets,
72 const Span<T> src,
74{
75 selection.foreach_index(GrainSize(512), [&](const int curve_i) {
76 const IndexRange src_points = src_points_by_curve[curve_i];
77 const IndexRange src_segments = bke::curves::per_curve_point_offsets_range(src_points,
78 curve_i);
79 const OffsetIndices<int> curve_offsets = all_point_offsets.slice(src_segments);
80 const IndexRange dst_points = dst_points_by_curve[curve_i];
81 const Span<T> curve_src = src.slice(src_points);
82 MutableSpan<T> curve_dst = dst.slice(dst_points);
83
84 threading::parallel_for(curve_src.index_range().drop_back(1), 1024, [&](IndexRange range) {
85 for (const int i : range) {
86 const IndexRange segment_points = curve_offsets[i];
87 linear_interpolation(curve_src[i], curve_src[i + 1], curve_dst.slice(segment_points));
88 }
89 });
90
91 const IndexRange dst_last_segment = dst_points.slice(curve_offsets[src_points.size() - 1]);
92 linear_interpolation(curve_src.last(), curve_src.first(), dst.slice(dst_last_segment));
93 });
94}
95
96static void subdivide_attribute_linear(const OffsetIndices<int> src_points_by_curve,
97 const OffsetIndices<int> dst_points_by_curve,
98 const IndexMask &selection,
99 const Span<int> all_point_offsets,
100 const GSpan src,
101 GMutableSpan dst)
102{
104 using T = decltype(dummy);
105 subdivide_attribute_linear(src_points_by_curve,
106 dst_points_by_curve,
107 selection,
108 all_point_offsets,
109 src.typed<T>(),
110 dst.typed<T>());
111 });
112}
113
114static void subdivide_attribute_catmull_rom(const OffsetIndices<int> src_points_by_curve,
115 const OffsetIndices<int> dst_points_by_curve,
116 const IndexMask &selection,
117 const Span<int> all_point_offsets,
118 const Span<bool> cyclic,
119 const GSpan src,
120 GMutableSpan dst)
121{
122 selection.foreach_index(GrainSize(512), [&](const int curve_i) {
123 const IndexRange src_points = src_points_by_curve[curve_i];
124 const IndexRange src_segments = bke::curves::per_curve_point_offsets_range(src_points,
125 curve_i);
126 const IndexRange dst_points = dst_points_by_curve[curve_i];
128 cyclic[curve_i],
129 all_point_offsets.slice(src_segments),
130 dst.slice(dst_points));
131 });
132}
133
135{
136 switch (type) {
138 return BEZIER_HANDLE_FREE;
140 return BEZIER_HANDLE_ALIGN;
142 return BEZIER_HANDLE_FREE;
144 return BEZIER_HANDLE_ALIGN;
145 }
147 return BEZIER_HANDLE_FREE;
148}
149
150static void subdivide_bezier_segment(const float3 &position_prev,
151 const float3 &handle_prev,
152 const float3 &handle_next,
153 const float3 &position_next,
154 const HandleType type_prev,
155 const HandleType type_next,
156 const IndexRange segment_points,
157 const int dst_next_segment_start,
158 MutableSpan<float3> dst_positions,
159 MutableSpan<float3> dst_handles_l,
160 MutableSpan<float3> dst_handles_r,
161 MutableSpan<int8_t> dst_types_l,
162 MutableSpan<int8_t> dst_types_r)
163{
164 if (bke::curves::bezier::segment_is_vector(type_prev, type_next)) {
165 linear_interpolation(position_prev, position_next, dst_positions.slice(segment_points));
166 /* All of the segment handles should be vector handles. */
167 dst_types_r[segment_points.first()] = BEZIER_HANDLE_VECTOR;
168 dst_types_l[dst_next_segment_start] = BEZIER_HANDLE_VECTOR;
169 dst_types_l.slice(segment_points.drop_front(1)).fill(BEZIER_HANDLE_VECTOR);
170 dst_types_r.slice(segment_points.drop_front(1)).fill(BEZIER_HANDLE_VECTOR);
171 }
172 else {
173 /* The first point in the segment is always copied. */
174 dst_positions[segment_points.first()] = position_prev;
175
176 /* In order to generate a Bezier curve with the same shape as the input curve, apply the
177 * De Casteljau algorithm iteratively for the provided number of cuts, constantly updating the
178 * previous result point's right handle and the left handle at the end of the segment. */
179 float3 segment_start = position_prev;
180 float3 segment_handle_prev = handle_prev;
181 float3 segment_handle_next = handle_next;
182 const float3 segment_end = position_next;
183
184 for (const int i : IndexRange(segment_points.size() - 1)) {
185 const float parameter = 1.0f / (segment_points.size() - i);
186 const int point_i = segment_points[i];
188 segment_start, segment_handle_prev, segment_handle_next, segment_end, parameter);
189
190 /* Copy relevant temporary data to the result. */
191 dst_handles_r[point_i] = insert.handle_prev;
192 dst_handles_l[point_i + 1] = insert.left_handle;
193 dst_positions[point_i + 1] = insert.position;
194
195 /* Update the segment to prepare it for the next subdivision. */
196 segment_start = insert.position;
197 segment_handle_prev = insert.right_handle;
198 segment_handle_next = insert.handle_next;
199 }
200
201 /* Copy the handles for the last segment from the working variables. */
202 dst_handles_r[segment_points.last()] = segment_handle_prev;
203 dst_handles_l[dst_next_segment_start] = segment_handle_next;
204
205 /* First and last handles at the ends of the segment are aligned if possible. */
206 dst_types_r[segment_points.first()] = aligned_or_free_handle_type(type_prev);
207 dst_types_l[dst_next_segment_start] = aligned_or_free_handle_type(type_next);
208
209 /* Handles inside the segment are aligned. */
210 dst_types_l.slice(segment_points.drop_front(1)).fill(BEZIER_HANDLE_ALIGN);
211 dst_types_r.slice(segment_points.drop_front(1)).fill(BEZIER_HANDLE_ALIGN);
212 }
213}
214
215static void subdivide_bezier_positions(const Span<float3> src_positions,
216 const Span<int8_t> src_types_l,
217 const Span<int8_t> src_types_r,
218 const Span<float3> src_handles_l,
219 const Span<float3> src_handles_r,
220 const OffsetIndices<int> evaluated_offsets,
221 const bool cyclic,
222 MutableSpan<float3> dst_positions,
223 MutableSpan<int8_t> dst_types_l,
224 MutableSpan<int8_t> dst_types_r,
225 MutableSpan<float3> dst_handles_l,
226 MutableSpan<float3> dst_handles_r)
227{
228 threading::parallel_for(src_positions.index_range().drop_back(1), 512, [&](IndexRange range) {
229 for (const int segment_i : range) {
230 const IndexRange segment = evaluated_offsets[segment_i];
231 subdivide_bezier_segment(src_positions[segment_i],
232 src_handles_r[segment_i],
233 src_handles_l[segment_i + 1],
234 src_positions[segment_i + 1],
235 HandleType(src_types_r[segment_i]),
236 HandleType(src_types_l[segment_i + 1]),
237 segment,
238 segment.one_after_last(),
239 dst_positions,
240 dst_handles_l,
241 dst_handles_r,
242 dst_types_l,
243 dst_types_r);
244 }
245 });
246
247 if (cyclic) {
248 const int last_index = src_positions.index_range().last();
249 const IndexRange segment = evaluated_offsets[last_index];
250 subdivide_bezier_segment(src_positions.last(),
251 src_handles_r.last(),
252 src_handles_l.first(),
253 src_positions.first(),
254 HandleType(src_types_r.last()),
255 HandleType(src_types_l.first()),
256 segment,
257 0,
258 dst_positions,
259 dst_handles_l,
260 dst_handles_r,
261 dst_types_l,
262 dst_types_r);
263 }
264 else {
265 dst_positions.last() = src_positions.last();
266 dst_types_l.first() = src_types_l.first();
267 dst_types_r.last() = src_types_r.last();
268 dst_handles_l.first() = src_handles_l.first();
269 dst_handles_r.last() = src_handles_r.last();
270 }
271
272 /* TODO: It would be possible to avoid calling this for all segments besides vector segments. */
273 bke::curves::bezier::calculate_auto_handles(
274 cyclic, dst_types_l, dst_types_r, dst_positions, dst_handles_l, dst_handles_r);
275}
276
278 const IndexMask &selection,
279 const VArray<int> &cuts,
280 const bke::AttributeFilter &attribute_filter)
281{
282 if (src_curves.is_empty()) {
283 return src_curves;
284 }
285
286 const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
287 /* Cyclic is accessed a lot, it's probably worth it to make sure it's a span. */
288 const VArraySpan<bool> cyclic{src_curves.cyclic()};
289 IndexMaskMemory memory;
290 const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
291
293 /* Copy vertex groups from source curves to allow copying vertex group attributes. */
295
296 /* For each point, this contains the point offset in the corresponding result curve,
297 * starting at zero. For example for two curves with four points each, the values might
298 * look like this:
299 *
300 * | | Curve 0 | Curve 1 |
301 * | ------------------- |---|---|---|---|---|---|---|---|---|----|
302 * | Cuts | 0 | 3 | 0 | 0 | - | 2 | 0 | 0 | 4 | - |
303 * | New Point Count | 1 | 4 | 1 | 1 | - | 3 | 1 | 1 | 5 | - |
304 * | Accumulated Offsets | 0 | 1 | 5 | 6 | 7 | 0 | 3 | 4 | 5 | 10 |
305 *
306 * Storing the leading zero is unnecessary but makes the array a bit simpler to use by avoiding
307 * a check for the first segment, and because some existing utilities also use leading zeros. */
308 Array<int> all_point_offset_data(src_curves.points_num() + src_curves.curves_num());
309#ifndef NDEBUG
310 all_point_offset_data.fill(-1);
311#endif
312 calculate_result_offsets(src_curves,
313 selection,
314 unselected,
315 cuts,
316 cyclic,
317 dst_curves.offsets_for_write(),
318 all_point_offset_data);
319 const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
320
321 const Span<int> all_point_offsets(all_point_offset_data);
322
323 dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num());
324
325 const bke::AttributeAccessor src_attributes = src_curves.attributes();
326 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
327
328 Vector<bke::AttributeTransferData> attributes_to_transfer =
330 src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, attribute_filter);
331
332 auto subdivide_catmull_rom = [&](const IndexMask &selection) {
333 for (auto &attribute : attributes_to_transfer) {
334 subdivide_attribute_catmull_rom(src_points_by_curve,
335 dst_points_by_curve,
336 selection,
337 all_point_offsets,
338 cyclic,
339 attribute.src,
340 attribute.dst.span);
341 }
342 };
343
344 auto subdivide_poly = [&](const IndexMask &selection) {
345 for (auto &attribute : attributes_to_transfer) {
346 subdivide_attribute_linear(src_points_by_curve,
347 dst_points_by_curve,
348 selection,
349 all_point_offsets,
350 attribute.src,
351 attribute.dst.span);
352 }
353 };
354
355 auto subdivide_bezier = [&](const IndexMask &selection) {
356 const Span<float3> src_positions = src_curves.positions();
357 const VArraySpan<int8_t> src_types_l{src_curves.handle_types_left()};
358 const VArraySpan<int8_t> src_types_r{src_curves.handle_types_right()};
359 const Span<float3> src_handles_l = *src_curves.handle_positions_left();
360 const Span<float3> src_handles_r = *src_curves.handle_positions_right();
361
362 MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
363 MutableSpan<int8_t> dst_types_l = dst_curves.handle_types_left_for_write();
364 MutableSpan<int8_t> dst_types_r = dst_curves.handle_types_right_for_write();
365 MutableSpan<float3> dst_handles_l = dst_curves.handle_positions_left_for_write();
366 MutableSpan<float3> dst_handles_r = dst_curves.handle_positions_right_for_write();
367 const OffsetIndices<int> dst_points_by_curve = dst_curves.points_by_curve();
368
369 selection.foreach_index(GrainSize(512), [&](const int curve_i) {
370 const IndexRange src_points = src_points_by_curve[curve_i];
371 const IndexRange src_segments = bke::curves::per_curve_point_offsets_range(src_points,
372 curve_i);
373 const IndexRange dst_points = dst_points_by_curve[curve_i];
374 subdivide_bezier_positions(src_positions.slice(src_points),
375 src_types_l.slice(src_points),
376 src_types_r.slice(src_points),
377 src_handles_l.slice(src_points),
378 src_handles_r.slice(src_points),
379 all_point_offsets.slice(src_segments),
380 cyclic[curve_i],
381 dst_positions.slice(dst_points),
382 dst_types_l.slice(dst_points),
383 dst_types_r.slice(dst_points),
384 dst_handles_l.slice(dst_points),
385 dst_handles_r.slice(dst_points));
386 });
387
388 /* Filter out positions and handles that are already interpolated. */
389 const Set<StringRef> attributes_to_skip = {
390 "position", "handle_type_left", "handle_type_right", "handle_right", "handle_left"};
391 for (auto &attribute : attributes_to_transfer) {
392 if (attributes_to_skip.contains(attribute.name)) {
393 continue;
394 }
395 subdivide_attribute_linear(src_points_by_curve,
396 dst_points_by_curve,
397 selection,
398 all_point_offsets,
399 attribute.src,
400 attribute.dst.span);
401 }
402 };
403
404 /* NURBS curves are just treated as poly curves. NURBS subdivision that maintains
405 * their shape may be possible, but probably wouldn't work with the "cuts" input. */
406 auto subdivide_nurbs = subdivide_poly;
407
409 src_curves.curve_type_counts(),
410 selection,
411 subdivide_catmull_rom,
412 subdivide_poly,
413 subdivide_bezier,
414 subdivide_nurbs);
415
416 for (auto &attribute : attributes_to_transfer) {
418 src_points_by_curve, dst_points_by_curve, unselected, attribute.src, attribute.dst.span);
419 attribute.dst.finish();
420 }
421
422 bke::curves::nurbs::copy_custom_knots(src_curves, selection, dst_curves);
423 return dst_curves;
424}
425
426} // namespace blender::geometry
@ ATTR_DOMAIN_MASK_POINT
Low-level operations for curves.
Low-level operations for curves.
support for deformation groups and hooks.
void BKE_defgroup_copy_list(ListBase *outbase, const ListBase *inbase)
Definition deform.cc:73
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
void BLI_kdtree_nd_ insert(KDTree *tree, int index, const float co[KD_DIMS]) ATTR_NONNULL(1
HandleType
@ BEZIER_HANDLE_FREE
@ BEZIER_HANDLE_ALIGN
@ BEZIER_HANDLE_VECTOR
@ BEZIER_HANDLE_AUTO
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:325
void fill(const T &value) const
Definition BLI_array.hh:272
GMutableSpan slice(const int64_t start, int64_t size) const
const CPPType & type() const
GSpan slice(const int64_t start, int64_t size) const
constexpr int64_t first() 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 IndexRange slice(int64_t start, int64_t size) 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 MutableSpan drop_back(const int64_t n) const
Definition BLI_span.hh:618
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 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 IndexRange index_range() const
Definition BLI_span.hh:401
void materialize_compressed(const IndexMask &mask, MutableSpan< T > r_span) const
VArray< int8_t > handle_types_left() const
MutableSpan< float3 > positions_for_write()
OffsetIndices< int > points_by_curve() const
MutableSpan< int8_t > handle_types_right_for_write()
VArray< int8_t > handle_types_right() const
IndexRange curves_range() const
const std::array< int, CURVE_TYPES_NUM > & curve_type_counts() const
MutableSpan< float3 > handle_positions_left_for_write()
MutableAttributeAccessor attributes_for_write()
MutableSpan< float3 > handle_positions_right_for_write()
std::optional< Span< float3 > > handle_positions_left() const
Span< float3 > positions() const
std::optional< Span< float3 > > handle_positions_right() const
void resize(int points_num, int curves_num)
AttributeAccessor attributes() const
MutableSpan< int > offsets_for_write()
VArray< int8_t > curve_types() const
VArray< bool > cyclic() const
MutableSpan< int8_t > handle_types_left_for_write()
IndexMask complement(const IndexMask &universe, IndexMaskMemory &memory) const
void foreach_index(Fn &&fn) const
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
int count
#define T
void copy_group_to_group(OffsetIndices< int > src_offsets, OffsetIndices< int > dst_offsets, const IndexMask &selection, GSpan src, GMutableSpan dst)
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)
bool segment_is_vector(const HandleType left, const HandleType right)
void interpolate_to_evaluated(GSpan src, bool cyclic, int resolution, GMutableSpan dst)
void copy_custom_knots(const bke::CurvesGeometry &src_curves, const IndexMask &exclude_curves, bke::CurvesGeometry &dst_curves)
bke::CurvesGeometry copy_only_curve_domain(const bke::CurvesGeometry &src_curves)
IndexRange per_curve_point_offsets_range(const IndexRange points, const int curve_index)
void foreach_curve_by_type(const VArray< int8_t > &types, const std::array< int, CURVE_TYPES_NUM > &type_counts, const 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 AttributeAccessor src_attributes, MutableAttributeAccessor dst_attributes, AttrDomainMask domain_mask, const AttributeFilter &attribute_filter={})
static HandleType aligned_or_free_handle_type(const HandleType type)
static void calculate_result_offsets(const OffsetIndices< int > src_points_by_curve, const IndexMask &selection, const IndexMask &unselected, 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_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 OffsetIndices< 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)
static void linear_interpolation(const T &a, const T &b, MutableSpan< T > dst)
bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, const IndexMask &selection, const VArray< int > &cuts, const bke::AttributeFilter &attribute_filter={})
static void subdivide_attribute_catmull_rom(const OffsetIndices< int > src_points_by_curve, const OffsetIndices< int > dst_points_by_curve, const IndexMask &selection, const Span< int > all_point_offsets, const Span< bool > cyclic, const GSpan src, GMutableSpan 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, const int dst_next_segment_start, 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)
static void subdivide_attribute_linear(const OffsetIndices< int > src_points_by_curve, const OffsetIndices< int > dst_points_by_curve, const IndexMask &selection, const Span< int > all_point_offsets, const Span< T > src, MutableSpan< T > dst)
void copy_group_sizes(OffsetIndices< int > offsets, const IndexMask &mask, MutableSpan< int > sizes)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
VecBase< float, 3 > float3
ListBase vertex_group_names
i
Definition text_draw.cc:230