Blender V4.3
fillet_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
10#include "BLI_task.hh"
11
12#include "GEO_fillet_curves.hh"
13
14namespace blender::geometry {
15
16static void duplicate_fillet_point_data(const OffsetIndices<int> src_points_by_curve,
17 const OffsetIndices<int> dst_points_by_curve,
18 const IndexMask &curve_selection,
19 const Span<int> all_point_offsets,
20 const GSpan src,
21 GMutableSpan dst)
22{
23 curve_selection.foreach_index(GrainSize(512), [&](const int curve_i) {
24 const IndexRange src_points = src_points_by_curve[curve_i];
25 const IndexRange dst_points = dst_points_by_curve[curve_i];
26 const IndexRange offsets_range = bke::curves::per_curve_point_offsets_range(src_points,
27 curve_i);
28 bke::attribute_math::gather_to_groups(all_point_offsets.slice(offsets_range),
29 IndexRange(src_points.size()),
30 src.slice(src_points),
31 dst.slice(dst_points));
32 });
33}
34
35static void calculate_result_offsets(const OffsetIndices<int> src_points_by_curve,
36 const IndexMask &selection,
37 const IndexMask &unselected,
38 const VArray<float> &radii,
39 const VArray<int> &counts,
40 const Span<bool> cyclic,
41 MutableSpan<int> dst_curve_offsets,
42 MutableSpan<int> dst_point_offsets)
43{
44 /* Fill the offsets array with the curve point counts, then accumulate them to form offsets. */
45 offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_curve_offsets);
46 selection.foreach_index(GrainSize(512), [&](const int curve_i) {
47 const IndexRange src_points = src_points_by_curve[curve_i];
48 const IndexRange offsets_range = bke::curves::per_curve_point_offsets_range(src_points,
49 curve_i);
50
51 MutableSpan<int> point_offsets = dst_point_offsets.slice(offsets_range);
52 MutableSpan<int> point_counts = point_offsets.drop_back(1);
53
54 counts.materialize_compressed(src_points, point_counts);
55 for (int &count : point_counts) {
56 /* Make sure the number of cuts is greater than zero and add one for the existing point. */
57 count = std::max(count, 0) + 1;
58 }
59 if (!cyclic[curve_i]) {
60 /* Endpoints on non-cyclic curves cannot be filleted. */
61 point_counts.first() = 1;
62 point_counts.last() = 1;
63 }
64 /* Implicitly "deselect" points with zero radius. */
65 devirtualize_varray(radii, [&](const auto radii) {
66 for (const int i : IndexRange(src_points.size())) {
67 if (radii[src_points[i]] == 0.0f) {
68 point_counts[i] = 1;
69 }
70 }
71 });
72
74
75 dst_curve_offsets[curve_i] = point_offsets.last();
76 });
78}
79
80static void calculate_directions(const Span<float3> positions, MutableSpan<float3> directions)
81{
82 for (const int i : positions.index_range().drop_back(1)) {
83 directions[i] = math::normalize(positions[i + 1] - positions[i]);
84 }
85 directions.last() = math::normalize(positions.first() - positions.last());
86}
87
88static void calculate_angles(const Span<float3> directions, MutableSpan<float> angles)
89{
90 angles.first() = M_PI - angle_v3v3(-directions.last(), directions.first());
91 for (const int i : directions.index_range().drop_front(1)) {
92 angles[i] = M_PI - angle_v3v3(-directions[i - 1], directions[i]);
93 }
94}
95
101static float limit_radius(const float3 &position_prev,
102 const float3 &position,
103 const float3 &position_next,
104 const float angle_prev,
105 const float angle,
106 const float angle_next,
107 const float radius_prev,
108 const float radius,
109 const float radius_next)
110{
111 const float displacement = radius * std::tan(angle / 2.0f);
112
113 const float displacement_prev = radius_prev * std::tan(angle_prev / 2.0f);
114 const float segment_length_prev = math::distance(position, position_prev);
115 const float total_displacement_prev = displacement_prev + displacement;
116 const float factor_prev = std::clamp(
117 math::safe_divide(segment_length_prev, total_displacement_prev), 0.0f, 1.0f);
118
119 const float displacement_next = radius_next * std::tan(angle_next / 2.0f);
120 const float segment_length_next = math::distance(position, position_next);
121 const float total_displacement_next = displacement_next + displacement;
122 const float factor_next = std::clamp(
123 math::safe_divide(segment_length_next, total_displacement_next), 0.0f, 1.0f);
124
125 return radius * std::min(factor_prev, factor_next);
126}
127
128static void limit_radii(const Span<float3> positions,
129 const Span<float> angles,
130 const Span<float> radii,
131 const bool cyclic,
132 MutableSpan<float> radii_clamped)
133{
134 if (cyclic) {
135 /* First point. */
136 radii_clamped.first() = limit_radius(positions.last(),
137 positions.first(),
138 positions[1],
139 angles.last(),
140 angles.first(),
141 angles[1],
142 radii.last(),
143 radii.first(),
144 radii[1]);
145 /* All middle points. */
146 for (const int i : positions.index_range().drop_back(1).drop_front(1)) {
147 const int i_prev = i - 1;
148 const int i_next = i + 1;
149 radii_clamped[i] = limit_radius(positions[i_prev],
150 positions[i],
151 positions[i_next],
152 angles[i_prev],
153 angles[i],
154 angles[i_next],
155 radii[i_prev],
156 radii[i],
157 radii[i_next]);
158 }
159 /* Last point. */
160 radii_clamped.last() = limit_radius(positions.last(1),
161 positions.last(),
162 positions.first(),
163 angles.last(1),
164 angles.last(),
165 angles.first(),
166 radii.last(1),
167 radii.last(),
168 radii.first());
169 }
170 else {
171 const int i_last = positions.index_range().last();
172 /* First point. */
173 radii_clamped.first() = 0.0f;
174 /* All middle points. */
175 for (const int i : positions.index_range().drop_back(1).drop_front(1)) {
176 const int i_prev = i - 1;
177 const int i_next = i + 1;
178 /* Use a zero radius for the first and last points, because they don't have fillets.
179 * This logic could potentially be unrolled, but it doesn't seem worth it. */
180 const float radius_prev = i_prev == 0 ? 0.0f : radii[i_prev];
181 const float radius_next = i_next == i_last ? 0.0f : radii[i_next];
182 radii_clamped[i] = limit_radius(positions[i_prev],
183 positions[i],
184 positions[i_next],
185 angles[i_prev],
186 angles[i],
187 angles[i_next],
188 radius_prev,
189 radii[i],
190 radius_next);
191 }
192 /* Last point. */
193 radii_clamped.last() = 0.0f;
194 }
195}
196
197static void calculate_fillet_positions(const Span<float3> src_positions,
198 const Span<float> angles,
199 const Span<float> radii,
200 const Span<float3> directions,
201 const OffsetIndices<int> dst_offsets,
203{
204 const int i_src_last = src_positions.index_range().last();
205 threading::parallel_for(src_positions.index_range(), 512, [&](IndexRange range) {
206 for (const int i_src : range) {
207 const IndexRange arc = dst_offsets[i_src];
208 const float3 &src = src_positions[i_src];
209 if (arc.size() == 1) {
210 dst[arc.first()] = src;
211 continue;
212 }
213
214 const int i_src_prev = i_src == 0 ? i_src_last : i_src - 1;
215 const float angle = angles[i_src];
216 const float radius = radii[i_src];
217 const float displacement = radius * std::tan(angle / 2.0f);
218 const float3 prev_dir = -directions[i_src_prev];
219 const float3 &next_dir = directions[i_src];
220 const float3 arc_start = src + prev_dir * displacement;
221 const float3 arc_end = src + next_dir * displacement;
222
223 dst[arc.first()] = arc_start;
224 dst[arc.last()] = arc_end;
225
226 const IndexRange middle = arc.drop_front(1).drop_back(1);
227 if (middle.is_empty()) {
228 continue;
229 }
230
231 const float3 axis = -math::normalize(math::cross(prev_dir, next_dir));
232 const float3 center_direction = math::normalize(math::midpoint(next_dir, prev_dir));
233 const float distance_to_center = std::sqrt(pow2f(radius) + pow2f(displacement));
234 const float3 center = src + center_direction * distance_to_center;
235
236 /* Rotate each middle fillet point around the center. */
237 const float segment_angle = angle / (middle.size() + 1);
238 for (const int i : IndexRange(middle.size())) {
239 const int point_i = middle[i];
240 dst[point_i] = math::rotate_around_axis(arc_start, center, axis, segment_angle * (i + 1));
241 }
242 }
243 });
244}
245
252 const Span<float3> src_handles_r,
253 const Span<int8_t> src_types_l,
254 const Span<int8_t> src_types_r,
255 const Span<float> angles,
256 const Span<float> radii,
257 const Span<float3> directions,
258 const OffsetIndices<int> dst_offsets,
259 const Span<float3> dst_positions,
260 MutableSpan<float3> dst_handles_l,
261 MutableSpan<float3> dst_handles_r,
262 MutableSpan<int8_t> dst_types_l,
263 MutableSpan<int8_t> dst_types_r)
264{
265 const int i_src_last = src_handles_l.index_range().last();
266 const int i_dst_last = dst_positions.index_range().last();
267 threading::parallel_for(src_handles_l.index_range(), 512, [&](IndexRange range) {
268 for (const int i_src : range) {
269 const IndexRange arc = dst_offsets[i_src];
270 if (arc.size() == 1) {
271 dst_handles_l[arc.first()] = src_handles_l[i_src];
272 dst_handles_r[arc.first()] = src_handles_r[i_src];
273 dst_types_l[arc.first()] = src_types_l[i_src];
274 dst_types_r[arc.first()] = src_types_r[i_src];
275 continue;
276 }
277 BLI_assert(arc.size() == 2);
278 const int i_dst_a = arc.first();
279 const int i_dst_b = arc.last();
280
281 const int i_src_prev = i_src == 0 ? i_src_last : i_src - 1;
282 const float angle = angles[i_src];
283 const float radius = radii[i_src];
284 const float3 prev_dir = -directions[i_src_prev];
285 const float3 &next_dir = directions[i_src];
286
287 const float3 &arc_start = dst_positions[arc.first()];
288 const float3 &arc_end = dst_positions[arc.last()];
289
290 /* Calculate the point's handles on the outside of the fillet segment,
291 * connecting to the next or previous result points. */
292 const int i_dst_prev = i_dst_a == 0 ? i_dst_last : i_dst_a - 1;
293 const int i_dst_next = i_dst_b == i_dst_last ? 0 : i_dst_b + 1;
294 dst_handles_l[i_dst_a] = bke::curves::bezier::calculate_vector_handle(
295 dst_positions[i_dst_a], dst_positions[i_dst_prev]);
296 dst_handles_r[i_dst_b] = bke::curves::bezier::calculate_vector_handle(
297 dst_positions[i_dst_b], dst_positions[i_dst_next]);
298 dst_types_l[i_dst_a] = BEZIER_HANDLE_VECTOR;
299 dst_types_r[i_dst_b] = BEZIER_HANDLE_VECTOR;
300
301 /* The inner handles are aligned with the aligned with the outer vector
302 * handles, but have a specific length to best approximate a circle. */
303 const float handle_length = (4.0f / 3.0f) * radius * std::tan(angle / 4.0f);
304 dst_handles_r[i_dst_a] = arc_start - prev_dir * handle_length;
305 dst_handles_l[i_dst_b] = arc_end - next_dir * handle_length;
306 dst_types_r[i_dst_a] = BEZIER_HANDLE_ALIGN;
307 dst_types_l[i_dst_b] = BEZIER_HANDLE_ALIGN;
308 }
309 });
310}
311
316static void calculate_bezier_handles_poly_mode(const Span<float3> src_handles_l,
317 const Span<float3> src_handles_r,
318 const Span<int8_t> src_types_l,
319 const Span<int8_t> src_types_r,
320 const OffsetIndices<int> dst_offsets,
321 const Span<float3> dst_positions,
322 MutableSpan<float3> dst_handles_l,
323 MutableSpan<float3> dst_handles_r,
324 MutableSpan<int8_t> dst_types_l,
325 MutableSpan<int8_t> dst_types_r)
326{
327 const int i_dst_last = dst_positions.index_range().last();
328 threading::parallel_for(src_handles_l.index_range(), 512, [&](IndexRange range) {
329 for (const int i_src : range) {
330 const IndexRange arc = dst_offsets[i_src];
331 if (arc.size() == 1) {
332 dst_handles_l[arc.first()] = src_handles_l[i_src];
333 dst_handles_r[arc.first()] = src_handles_r[i_src];
334 dst_types_l[arc.first()] = src_types_l[i_src];
335 dst_types_r[arc.first()] = src_types_r[i_src];
336 continue;
337 }
338
339 /* The fillet's next and previous handles are vector handles, as are the inner handles. */
340 dst_types_l.slice(arc).fill(BEZIER_HANDLE_VECTOR);
341 dst_types_r.slice(arc).fill(BEZIER_HANDLE_VECTOR);
342
343 /* Calculate the point's handles on the outside of the fillet segment. This point
344 * won't be selected for a fillet if it is the first or last in a non-cyclic curve. */
345
346 const int i_dst_prev = arc.first() == 0 ? i_dst_last : arc.one_before_start();
347 const int i_dst_next = arc.last() == i_dst_last ? 0 : arc.one_after_last();
348 dst_handles_l[arc.first()] = bke::curves::bezier::calculate_vector_handle(
349 dst_positions[arc.first()], dst_positions[i_dst_prev]);
350 dst_handles_r[arc.last()] = bke::curves::bezier::calculate_vector_handle(
351 dst_positions[arc.last()], dst_positions[i_dst_next]);
352
353 /* Set the values for the inner handles. */
354 const IndexRange middle = arc.drop_front(1).drop_back(1);
355 for (const int i : middle) {
356 dst_handles_r[i] = bke::curves::bezier::calculate_vector_handle(dst_positions[i],
357 dst_positions[i - 1]);
358 dst_handles_l[i] = bke::curves::bezier::calculate_vector_handle(dst_positions[i],
359 dst_positions[i + 1]);
360 }
361 }
362 });
363}
364
366 const IndexMask &curve_selection,
367 const VArray<float> &radius_input,
368 const VArray<int> &counts,
369 const bool limit_radius,
370 const bool use_bezier_mode,
371 const bke::AttributeFilter &attribute_filter)
372{
373 const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
374 const Span<float3> positions = src_curves.positions();
375 const VArraySpan<bool> cyclic{src_curves.cyclic()};
376 const bke::AttributeAccessor src_attributes = src_curves.attributes();
377 IndexMaskMemory memory;
378 const IndexMask unselected = curve_selection.complement(src_curves.curves_range(), memory);
379
380 bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
381 /* Stores the offset of every result point for every original point.
382 * The extra length is used in order to store an extra zero for every curve. */
383 Array<int> dst_point_offsets(src_curves.points_num() + src_curves.curves_num());
384 calculate_result_offsets(src_points_by_curve,
385 curve_selection,
386 unselected,
387 radius_input,
388 counts,
389 cyclic,
390 dst_curves.offsets_for_write(),
391 dst_point_offsets);
392 const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
393 const Span<int> all_point_offsets = dst_point_offsets.as_span();
394
395 dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num());
396 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
397 MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
398
399 VArraySpan<int8_t> src_types_l;
400 VArraySpan<int8_t> src_types_r;
401 Span<float3> src_handles_l;
402 Span<float3> src_handles_r;
403 MutableSpan<int8_t> dst_types_l;
404 MutableSpan<int8_t> dst_types_r;
405 MutableSpan<float3> dst_handles_l;
406 MutableSpan<float3> dst_handles_r;
407 if (src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
408 src_types_l = src_curves.handle_types_left();
409 src_types_r = src_curves.handle_types_right();
410 src_handles_l = src_curves.handle_positions_left();
411 src_handles_r = src_curves.handle_positions_right();
412
413 dst_types_l = dst_curves.handle_types_left_for_write();
414 dst_types_r = dst_curves.handle_types_right_for_write();
415 dst_handles_l = dst_curves.handle_positions_left_for_write();
416 dst_handles_r = dst_curves.handle_positions_right_for_write();
417 }
418
419 curve_selection.foreach_segment(GrainSize(512), [&](const IndexMaskSegment segment) {
420 Array<float3> directions;
421 Array<float> angles;
422 Array<float> radii;
423 Array<float> input_radii_buffer;
424
425 for (const int curve_i : segment) {
426 const IndexRange src_points = src_points_by_curve[curve_i];
427 const IndexRange offsets_range = bke::curves::per_curve_point_offsets_range(src_points,
428 curve_i);
429 const OffsetIndices<int> offsets(all_point_offsets.slice(offsets_range));
430 const IndexRange dst_points = dst_points_by_curve[curve_i];
431 const Span<float3> src_positions = positions.slice(src_points);
432
433 directions.reinitialize(src_points.size());
434 calculate_directions(src_positions, directions);
435
436 angles.reinitialize(src_points.size());
437 calculate_angles(directions, angles);
438
439 radii.reinitialize(src_points.size());
440 if (limit_radius) {
441 input_radii_buffer.reinitialize(src_points.size());
442 radius_input.materialize_compressed(src_points, input_radii_buffer);
443 limit_radii(src_positions, angles, input_radii_buffer, cyclic[curve_i], radii);
444 }
445 else {
446 radius_input.materialize_compressed(src_points, radii);
447 }
448
449 calculate_fillet_positions(positions.slice(src_points),
450 angles,
451 radii,
452 directions,
453 offsets,
454 dst_positions.slice(dst_points));
455
456 if (src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
457 if (use_bezier_mode) {
458 calculate_bezier_handles_bezier_mode(src_handles_l.slice(src_points),
459 src_handles_r.slice(src_points),
460 src_types_l.slice(src_points),
461 src_types_r.slice(src_points),
462 angles,
463 radii,
464 directions,
465 offsets,
466 dst_positions.slice(dst_points),
467 dst_handles_l.slice(dst_points),
468 dst_handles_r.slice(dst_points),
469 dst_types_l.slice(dst_points),
470 dst_types_r.slice(dst_points));
471 }
472 else {
473 calculate_bezier_handles_poly_mode(src_handles_l.slice(src_points),
474 src_handles_r.slice(src_points),
475 src_types_l.slice(src_points),
476 src_types_r.slice(src_points),
477 offsets,
478 dst_positions.slice(dst_points),
479 dst_handles_l.slice(dst_points),
480 dst_handles_r.slice(dst_points),
481 dst_types_l.slice(dst_points),
482 dst_types_r.slice(dst_points));
483 }
484 }
485 }
486 });
487
488 for (auto &attribute : bke::retrieve_attributes_for_transfer(
489 src_attributes,
490 dst_attributes,
492 bke::attribute_filter_with_skip_ref(attribute_filter,
493 {"position",
494 "handle_type_left",
495 "handle_type_right",
496 "handle_right",
497 "handle_left"})))
498 {
499 duplicate_fillet_point_data(src_points_by_curve,
500 dst_points_by_curve,
501 curve_selection,
502 all_point_offsets,
503 attribute.src,
504 attribute.dst.span);
505 attribute.dst.finish();
506 }
507
508 bke::copy_attributes_group_to_group(src_attributes,
509 bke::AttrDomain::Point,
510 bke::AttrDomain::Point,
511 attribute_filter,
512 src_points_by_curve,
513 dst_points_by_curve,
514 unselected,
515 dst_attributes);
516
517 return dst_curves;
518}
519
521 const IndexMask &curve_selection,
522 const VArray<float> &radius,
523 const VArray<int> &count,
524 const bool limit_radius,
525 const bke::AttributeFilter &attribute_filter)
526{
527 return fillet_curves(
528 src_curves, curve_selection, radius, count, limit_radius, false, attribute_filter);
529}
530
532 const IndexMask &curve_selection,
533 const VArray<float> &radius,
534 const bool limit_radius,
535 const bke::AttributeFilter &attribute_filter)
536{
537 return fillet_curves(src_curves,
538 curve_selection,
539 radius,
540 VArray<int>::ForSingle(1, src_curves.points_num()),
542 true,
543 attribute_filter);
544}
545
546} // namespace blender::geometry
@ ATTR_DOMAIN_MASK_POINT
Low-level operations for curves.
Low-level operations for curves.
#define M_PI
float angle_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
@ CURVE_TYPE_BEZIER
Span< T > as_span() const
Definition BLI_array.hh:232
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:388
GMutableSpan slice(const int64_t start, int64_t size) const
GSpan slice(const int64_t start, int64_t size) const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr IndexRange drop_front(int64_t n) const
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 T & first() const
Definition BLI_span.hh:680
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 & first() const
Definition BLI_span.hh:316
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
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
bool has_curve_with_type(CurveType type) const
void resize(int points_num, int curves_num)
Span< float3 > handle_positions_right() const
MutableSpan< int > offsets_for_write()
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
void foreach_segment(Fn &&fn) const
int count
void gather_to_groups(OffsetIndices< int > dst_offsets, const IndexMask &src_selection, GSpan src, GMutableSpan dst)
IndexRange per_curve_point_offsets_range(const IndexRange points, const int curve_index)
static void calculate_bezier_handles_poly_mode(const Span< float3 > src_handles_l, const Span< float3 > src_handles_r, const Span< int8_t > src_types_l, const Span< int8_t > src_types_r, const OffsetIndices< int > dst_offsets, const Span< 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)
bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves, const IndexMask &curve_selection, const VArray< float > &radius, bool limit_radius, const bke::AttributeFilter &attribute_filter)
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 limit_radii(const Span< float3 > positions, const Span< float > angles, const Span< float > radii, const bool cyclic, MutableSpan< float > radii_clamped)
static void calculate_angles(const Span< float3 > directions, MutableSpan< float > angles)
static float limit_radius(const float3 &position_prev, const float3 &position, const float3 &position_next, const float angle_prev, const float angle, const float angle_next, const float radius_prev, const float radius, const float radius_next)
bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves, const IndexMask &curve_selection, const VArray< float > &radius, const VArray< int > &counts, bool limit_radius, const bke::AttributeFilter &attribute_filter)
static void calculate_fillet_positions(const Span< float3 > src_positions, const Span< float > angles, const Span< float > radii, const Span< float3 > directions, const OffsetIndices< int > dst_offsets, MutableSpan< float3 > dst)
static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves, const IndexMask &curve_selection, const VArray< float > &radius_input, const VArray< int > &counts, const bool limit_radius, const bool use_bezier_mode, const bke::AttributeFilter &attribute_filter)
static void calculate_directions(const Span< float3 > positions, MutableSpan< float3 > directions)
static void calculate_bezier_handles_bezier_mode(const Span< float3 > src_handles_l, const Span< float3 > src_handles_r, const Span< int8_t > src_types_l, const Span< int8_t > src_types_r, const Span< float > angles, const Span< float > radii, const Span< float3 > directions, const OffsetIndices< int > dst_offsets, const Span< 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 duplicate_fillet_point_data(const OffsetIndices< int > src_points_by_curve, const OffsetIndices< int > dst_points_by_curve, const IndexMask &curve_selection, const Span< int > all_point_offsets, const GSpan src, GMutableSpan dst)
T safe_divide(const T &a, const T &b)
T distance(const T &a, const T &b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
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
void devirtualize_varray(const VArray< T > &varray, const Func &func, bool enable=true)