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