Blender V5.0
curve_bezier.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
8
9#include <algorithm>
10
11#include "BLI_task.hh"
12
13#include "BKE_attribute_math.hh"
14#include "BKE_curves.hh"
15
17
18bool segment_is_vector(const Span<int8_t> handle_types_left,
19 const Span<int8_t> handle_types_right,
20 const int segment_index)
21{
22 BLI_assert(handle_types_left.index_range().drop_back(1).contains(segment_index));
23 return segment_is_vector(handle_types_right[segment_index],
24 handle_types_left[segment_index + 1]);
25}
26
27bool last_cyclic_segment_is_vector(const Span<int8_t> handle_types_left,
28 const Span<int8_t> handle_types_right)
29{
30 return segment_is_vector(handle_types_right.last(), handle_types_left.first());
31}
32
33void calculate_evaluated_offsets(const Span<int8_t> handle_types_left,
34 const Span<int8_t> handle_types_right,
35 const bool cyclic,
36 const int resolution,
37 MutableSpan<int> evaluated_offsets)
38{
39 const int size = handle_types_left.size();
40 BLI_assert(evaluated_offsets.size() == size + 1);
41
42 evaluated_offsets.first() = 0;
43 if (size == 1) {
44 evaluated_offsets.last() = 1;
45 return;
46 }
47
48 const int points_per_segment = std::max(1, resolution);
49
50 int offset = 0;
51 for (const int i : IndexRange(size - 1)) {
52 evaluated_offsets[i] = offset;
53 offset += segment_is_vector(handle_types_left, handle_types_right, i) ? 1 : points_per_segment;
54 }
55
56 evaluated_offsets.last(1) = offset;
57 if (cyclic) {
58 offset += last_cyclic_segment_is_vector(handle_types_left, handle_types_right) ?
59 1 :
60 points_per_segment;
61 }
62 else {
63 offset++;
64 }
65
66 evaluated_offsets.last() = offset;
67}
68
69Insertion insert(const float3 &point_prev,
70 const float3 &handle_prev,
71 const float3 &handle_next,
72 const float3 &point_next,
73 float parameter)
74{
75 /* De Casteljau Bezier subdivision. */
76 BLI_assert(parameter <= 1.0f && parameter >= 0.0f);
77
78 const float3 center_point = math::interpolate(handle_prev, handle_next, parameter);
79
81 result.handle_prev = math::interpolate(point_prev, handle_prev, parameter);
82 result.handle_next = math::interpolate(handle_next, point_next, parameter);
83 result.left_handle = math::interpolate(result.handle_prev, center_point, parameter);
84 result.right_handle = math::interpolate(center_point, result.handle_next, parameter);
85 result.position = math::interpolate(result.left_handle, result.right_handle, parameter);
86 return result;
87}
88
89static float3 calculate_aligned_handle(const float3 &position,
90 const float3 &other_handle,
91 const float3 &aligned_handle)
92{
93 /* Keep track of the old length of the opposite handle. */
94 const float length = math::distance(aligned_handle, position);
95 /* Set the other handle to directly opposite from the current handle. */
96 const float3 dir = math::normalize(other_handle - position);
97 return position - dir * length;
98}
99
100static void calculate_point_handles(const HandleType type_left,
101 const HandleType type_right,
102 const float3 position,
103 const float3 prev_position,
104 const float3 next_position,
105 float3 &left,
106 float3 &right)
107{
108 if (ELEM(BEZIER_HANDLE_AUTO, type_left, type_right)) {
109 const float3 prev_diff = position - prev_position;
110 const float3 next_diff = next_position - position;
111 float prev_len = math::length(prev_diff);
112 float next_len = math::length(next_diff);
113 if (prev_len == 0.0f) {
114 prev_len = 1.0f;
115 }
116 if (next_len == 0.0f) {
117 next_len = 1.0f;
118 }
119 const float3 dir = next_diff / next_len + prev_diff / prev_len;
120
121 /* The magic number 2.5614 is derived from approximating a circular arc at the control point.
122 * Given the constraints:
123 *
124 * - `P0=(0,1),P1=(c,1),P2=(1,c),P3=(1,0)`.
125 * - The first derivative of the curve must agree with the circular arc derivative at the
126 * endpoints.
127 * - Minimize the maximum radial drift.
128 * one can compute `c ≈ 0.5519150244935105707435627`.
129 * The distance from P0 to P3 is `sqrt(2)`.
130 *
131 * The magic factor for `len` is `(sqrt(2) / 0.5519150244935105707435627) ≈ 2.562375546255352`.
132 * In older code of blender a slightly worse approximation of 2.5614 is used. It's kept
133 * for compatibility.
134 *
135 * See https://spencermortensen.com/articles/bezier-circle/. */
136 const float len = math::length(dir) * 2.5614f;
137 if (len != 0.0f) {
138 if (type_left == BEZIER_HANDLE_AUTO) {
139 const float prev_len_clamped = std::min(prev_len, next_len * 5.0f);
140 left = position + dir * -(prev_len_clamped / len);
141 }
142 if (type_right == BEZIER_HANDLE_AUTO) {
143 const float next_len_clamped = std::min(next_len, prev_len * 5.0f);
144 right = position + dir * (next_len_clamped / len);
145 }
146 }
147 }
148
149 if (type_left == BEZIER_HANDLE_VECTOR) {
150 left = calculate_vector_handle(position, prev_position);
151 }
152
153 if (type_right == BEZIER_HANDLE_VECTOR) {
154 right = calculate_vector_handle(position, next_position);
155 }
156
157 /* When one of the handles is "aligned" handle, it must be aligned with the other, i.e. point in
158 * the opposite direction. Don't handle the case of two aligned handles, because code elsewhere
159 * should keep the pair consistent, and the relative locations aren't affected by other points
160 * anyway. */
161 if (type_left == BEZIER_HANDLE_ALIGN && type_right != BEZIER_HANDLE_ALIGN) {
162 left = calculate_aligned_handle(position, right, left);
163 }
164 else if (type_left != BEZIER_HANDLE_ALIGN && type_right == BEZIER_HANDLE_ALIGN) {
165 right = calculate_aligned_handle(position, left, right);
166 }
167}
168
169void set_handle_position(const float3 &position,
170 const HandleType type,
171 const HandleType type_other,
172 const float3 &new_handle,
173 float3 &handle,
174 float3 &handle_other)
175{
176 /* Don't bother when the handle positions are calculated automatically anyway. */
178 return;
179 }
180
181 handle = new_handle;
182 if (type_other == BEZIER_HANDLE_ALIGN) {
183 handle_other = calculate_aligned_handle(position, handle, handle_other);
184 }
185}
186
188 const Span<float3> positions,
189 const Span<float3> align_with,
190 MutableSpan<float3> align_handles)
191{
192 selection.foreach_index_optimized<int>(GrainSize(4096), [&](const int point) {
193 align_handles[point] = calculate_aligned_handle(
194 positions[point], align_with[point], align_handles[point]);
195 });
196}
197
198void calculate_auto_handles(const bool cyclic,
199 const Span<int8_t> types_left,
200 const Span<int8_t> types_right,
201 const Span<float3> positions,
202 MutableSpan<float3> positions_left,
203 MutableSpan<float3> positions_right)
204{
205 const int points_num = positions.size();
206 if (points_num == 1) {
207 return;
208 }
209
211 HandleType(types_right.first()),
212 positions.first(),
213 cyclic ? positions.last() : 2.0f * positions.first() - positions[1],
214 positions[1],
215 positions_left.first(),
216 positions_right.first());
217
218 threading::parallel_for(IndexRange(1, points_num - 2), 1024, [&](IndexRange range) {
219 for (const int i : range) {
221 HandleType(types_right[i]),
222 positions[i],
223 positions[i - 1],
224 positions[i + 1],
225 positions_left[i],
226 positions_right[i]);
227 }
228 });
229
231 HandleType(types_right.last()),
232 positions.last(),
233 positions.last(1),
234 cyclic ? positions.first() : 2.0f * positions.last() - positions.last(1),
235 positions_left.last(),
236 positions_right.last());
237}
238
239template<typename T>
241 const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan<T> result)
242{
243 BLI_assert(result.size() > 0);
244 const float inv_len = 1.0f / float(result.size());
245 const float inv_len_squared = inv_len * inv_len;
246 const float inv_len_cubed = inv_len_squared * inv_len;
247
248 const T rt1 = 3.0f * (point_1 - point_0) * inv_len;
249 const T rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared;
250 const T rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed;
251
252 T q0 = point_0;
253 T q1 = rt1 + rt2 + rt3;
254 T q2 = 2.0f * rt2 + 6.0f * rt3;
255 T q3 = 6.0f * rt3;
256 for (const int i : result.index_range()) {
257 result[i] = q0;
258 q0 += q1;
259 q1 += q2;
260 q2 += q3;
261 }
262}
263template<>
264void evaluate_segment(const float3 &point_0,
265 const float3 &point_1,
266 const float3 &point_2,
267 const float3 &point_3,
269{
270 evaluate_segment_ex<float3>(point_0, point_1, point_2, point_3, result);
271}
272template<>
273void evaluate_segment(const float2 &point_0,
274 const float2 &point_1,
275 const float2 &point_2,
276 const float2 &point_3,
278{
279 evaluate_segment_ex<float2>(point_0, point_1, point_2, point_3, result);
280}
281
283 const Span<float3> handles_left,
284 const Span<float3> handles_right,
285 const OffsetIndices<int> evaluated_offsets,
286 MutableSpan<float3> evaluated_positions)
287{
288 BLI_assert(evaluated_offsets.total_size() == evaluated_positions.size());
289 if (evaluated_offsets.total_size() == 1) {
290 evaluated_positions.first() = positions.first();
291 return;
292 }
293
294 /* Evaluate the first segment. */
295 evaluate_segment(positions.first(),
296 handles_right.first(),
297 handles_left[1],
298 positions[1],
299 evaluated_positions.slice(evaluated_offsets[0]));
300
301 /* Give each task fewer segments as the resolution gets larger. */
302 const int grain_size = std::max<int>(evaluated_positions.size() / positions.size() * 32, 1);
303 const IndexRange inner_segments = positions.index_range().drop_back(1).drop_front(1);
304 threading::parallel_for(inner_segments, grain_size, [&](IndexRange range) {
305 for (const int i : range) {
306 const IndexRange evaluated_range = evaluated_offsets[i];
307 if (evaluated_range.size() == 1) {
308 evaluated_positions[evaluated_range.first()] = positions[i];
309 }
310 else {
311 evaluate_segment(positions[i],
312 handles_right[i],
313 handles_left[i + 1],
314 positions[i + 1],
315 evaluated_positions.slice(evaluated_range));
316 }
317 }
318 });
319
320 /* Evaluate the final cyclic segment if necessary. */
321 const IndexRange last_segment_points = evaluated_offsets[positions.index_range().last()];
322 if (last_segment_points.size() == 1) {
323 evaluated_positions.last() = positions.last();
324 }
325 else {
326 evaluate_segment(positions.last(),
327 handles_right.last(),
328 handles_left.first(),
329 positions.first(),
330 evaluated_positions.slice(last_segment_points));
331 }
332}
333
334template<typename T>
335static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> dst)
336{
337 dst.first() = a;
338 const float step = 1.0f / dst.size();
339 for (const int i : dst.index_range().drop_front(1)) {
340 dst[i] = attribute_math::mix2(i * step, a, b);
341 }
342}
343
344template<typename T>
345static void interpolate_to_evaluated(const Span<T> src,
346 const OffsetIndices<int> evaluated_offsets,
347 MutableSpan<T> dst)
348{
349 BLI_assert(!src.is_empty());
350 BLI_assert(evaluated_offsets.total_size() == dst.size());
351 if (src.size() == 1) {
352 BLI_assert(dst.size() == 1);
353 dst.first() = src.first();
354 return;
355 }
356
357 linear_interpolation(src.first(), src[1], dst.slice(evaluated_offsets[0]));
358
360 src.index_range().drop_back(1).drop_front(1), 512, [&](IndexRange range) {
361 for (const int i : range) {
362 const IndexRange segment = evaluated_offsets[i];
363 linear_interpolation(src[i], src[i + 1], dst.slice(segment));
364 }
365 });
366
367 const IndexRange last_segment = evaluated_offsets[src.index_range().last()];
368 linear_interpolation(src.last(), src.first(), dst.slice(last_segment));
369}
370
372 const OffsetIndices<int> evaluated_offsets,
373 GMutableSpan dst)
374{
375 attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
376 using T = decltype(dummy);
377 if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
378 interpolate_to_evaluated(src.typed<T>(), evaluated_offsets, dst.typed<T>());
379 }
380 });
381}
382
383} // namespace blender::bke::curves::bezier
Low-level operations for curves.
#define BLI_assert(a)
Definition BLI_assert.h:46
#define ELEM(...)
HandleType
@ BEZIER_HANDLE_ALIGN
@ BEZIER_HANDLE_VECTOR
@ BEZIER_HANDLE_AUTO
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
const CPPType & type() 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 bool contains(int64_t value) 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 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
constexpr const T & first() const
Definition BLI_span.hh:315
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:325
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
Definition BLI_span.hh:260
void foreach_index_optimized(Fn &&fn) const
nullptr float
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
float length(VecOp< float, D >) RET
static int left
#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 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 calculate_evaluated_offsets(Span< int8_t > handle_types_left, Span< int8_t > handle_types_right, bool cyclic, int resolution, MutableSpan< int > evaluated_offsets)
float3 calculate_vector_handle(const float3 &point, const float3 &next_point)
static void calculate_point_handles(const HandleType type_left, const HandleType type_right, const float3 position, const float3 prev_position, const float3 next_position, float3 &left, float3 &right)
void calculate_aligned_handles(const IndexMask &selection, Span< float3 > positions, Span< float3 > align_by, MutableSpan< float3 > align)
bool last_cyclic_segment_is_vector(Span< int8_t > handle_types_left, Span< int8_t > handle_types_right)
static float3 calculate_aligned_handle(const float3 &position, const float3 &other_handle, const float3 &aligned_handle)
void evaluate_segment(const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan< T > result)
void calculate_evaluated_positions(Span< float3 > positions, Span< float3 > handles_left, Span< float3 > handles_right, OffsetIndices< int > evaluated_offsets, MutableSpan< float3 > evaluated_positions)
void interpolate_to_evaluated(GSpan src, OffsetIndices< int > evaluated_offsets, GMutableSpan dst)
void evaluate_segment_ex(const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan< T > result)
void set_handle_position(const float3 &position, HandleType type, HandleType type_other, const float3 &new_handle, float3 &handle, float3 &handle_other)
static void linear_interpolation(const T &a, const T &b, MutableSpan< T > dst)
T distance(const T &a, const T &b)
T length(const VecBase< T, Size > &a)
T interpolate(const T &a, const T &b, const FactorT &t)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
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, 2 > float2
VecBase< float, 3 > float3
i
Definition text_draw.cc:230
uint len