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{
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];
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];
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
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) {
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) {
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 :
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
408 src_curves.curve_type_counts(),
409 selection,
410 subdivide_catmull_rom,
411 subdivide_poly,
412 subdivide_bezier,
413 subdivide_nurbs);
414
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
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
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:326
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 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()
local_group_size(16, 16) .push_constant(Type b
int count
#define T
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)
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={})
void copy_attributes_group_to_group(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, OffsetIndices< int > src_offsets, OffsetIndices< int > dst_offsets, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
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 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
VecBase< float, 3 > float3
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[]