Blender V4.5
curve_nurbs.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 "BLI_task.hh"
10
11#include "BKE_attribute_math.hh"
12#include "BKE_curves.hh"
13
15
16bool check_valid_num_and_order(const int points_num,
17 const int8_t order,
18 const bool cyclic,
19 const KnotsMode knots_mode)
20{
21 if (points_num < order) {
22 return false;
23 }
24
26 if (knots_mode == NURBS_KNOT_MODE_BEZIER && points_num <= order) {
27 return false;
28 }
29 return (!cyclic || points_num % (order - 1) == 0);
30 }
31
32 return true;
33}
34
35int calculate_evaluated_num(const int points_num,
36 const int8_t order,
37 const bool cyclic,
38 const int resolution,
39 const KnotsMode knots_mode)
40{
41 if (!check_valid_num_and_order(points_num, order, cyclic, knots_mode)) {
42 return points_num;
43 }
44 return resolution * segments_num(points_num, cyclic);
45}
46
47int knots_num(const int points_num, const int8_t order, const bool cyclic)
48{
49 if (cyclic) {
50 return points_num + order * 2 - 1;
51 }
52 return points_num + order;
53}
54
55static void copy_custom_knots(const int8_t order,
56 const bool cyclic,
57 const Span<float> custom_knots,
59{
60 knots.slice(0, custom_knots.size()).copy_from(custom_knots);
61 if (cyclic) {
62 const float last_knot = custom_knots.last();
63 const float shift = last_knot - knots[order - 1];
64 const MutableSpan<float> tail = knots.take_back(order - 1);
65 for (const int knot : tail.index_range()) {
66 tail[knot] = knots[order + knot] + shift;
67 }
68 }
69}
70
71void calculate_knots(const int points_num,
72 const KnotsMode mode,
73 const int8_t order,
74 const bool cyclic,
76{
77 BLI_assert(knots.size() == knots_num(points_num, order, cyclic));
78 UNUSED_VARS_NDEBUG(points_num);
79
80 const bool is_bezier = ELEM(mode, NURBS_KNOT_MODE_BEZIER, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
81 const bool is_end_point = ELEM(mode, NURBS_KNOT_MODE_ENDPOINT, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
82 /* Inner knots are always repeated once except on Bezier case. */
83 const int repeat_inner = is_bezier ? order - 1 : 1;
84 /* How many times to repeat 0.0 at the beginning of knot. */
85 const int head = is_end_point ? (order - (cyclic ? 1 : 0)) :
86 (is_bezier ? min_ii(2, repeat_inner) : 1);
87 /* Number of knots replicating widths of the starting knots.
88 * Covers both Cyclic and EndPoint cases. */
89 const int tail = cyclic ? 2 * order - 1 : (is_end_point ? order : 0);
90
91 int r = head;
92 float current = 0.0f;
93
94 const int offset = is_end_point && cyclic ? 1 : 0;
95 if (offset) {
96 knots[0] = current;
97 current += 1.0f;
98 }
99
100 for (const int i : IndexRange(offset, knots.size() - offset - tail)) {
101 knots[i] = current;
102 r--;
103 if (r == 0) {
104 current += 1.0;
105 r = repeat_inner;
106 }
107 }
108
109 const int tail_index = knots.size() - tail;
110 for (const int i : IndexRange(tail)) {
111 knots[tail_index + i] = current + (knots[i] - knots[0]);
112 }
113}
114
116 const int points_num,
117 const int8_t order,
118 const bool cyclic,
119 const IndexRange curve_knots,
120 const Span<float> custom_knots,
121 MutableSpan<float> knots)
122{
123 if (mode == NURBS_KNOT_MODE_CUSTOM) {
124 BLI_assert(!custom_knots.is_empty());
125 BLI_assert(!curve_knots.is_empty());
126 copy_custom_knots(order, cyclic, custom_knots.slice(curve_knots), knots);
127 }
128 else {
129 calculate_knots(points_num, mode, order, cyclic, knots);
130 }
131}
132
134{
135 Vector<int> multiplicity;
136 multiplicity.reserve(knots.size());
137
138 int m = 1;
139 for (const int64_t i : knots.index_range().drop_front(1)) {
140 /* Only consider multiplicity for exact matching values. */
141 if (knots[i - 1] == knots[i]) {
142 m++;
143 }
144 else {
145 multiplicity.append(m);
146 m = 1;
147 }
148 }
149 multiplicity.append(m);
150 return multiplicity;
151}
152
153static void calculate_basis_for_point(const float parameter,
154 const int points_num,
155 const int degree,
156 const Span<float> knots,
157 MutableSpan<float> r_weights,
158 int &r_start_index)
159{
160 const int order = degree + 1;
161
162 int start = 0;
163 int end = 0;
164 for (const int i : IndexRange(points_num + degree)) {
165 const bool knots_equal = knots[i] == knots[i + 1];
166 if (knots_equal || parameter < knots[i] || parameter > knots[i + 1]) {
167 continue;
168 }
169
170 start = std::max(i - degree, 0);
171 end = i;
172 break;
173 }
174
175 Array<float, 12> buffer(order * 2, 0.0f);
176
177 buffer[end - start] = 1.0f;
178
179 for (const int i_order : IndexRange(2, degree)) {
180 if (end + i_order >= knots.size()) {
181 end = points_num + degree - i_order;
182 }
183 for (const int i : IndexRange(end - start + 1)) {
184 const int knot_index = start + i;
185
186 float new_basis = 0.0f;
187 if (buffer[i] != 0.0f) {
188 new_basis += ((parameter - knots[knot_index]) * buffer[i]) /
189 (knots[knot_index + i_order - 1] - knots[knot_index]);
190 }
191
192 if (buffer[i + 1] != 0.0f) {
193 new_basis += ((knots[knot_index + i_order] - parameter) * buffer[i + 1]) /
194 (knots[knot_index + i_order] - knots[knot_index + 1]);
195 }
196
197 buffer[i] = new_basis;
198 }
199 }
200
201 buffer.as_mutable_span().drop_front(end - start + 1).fill(0.0f);
202 r_weights.copy_from(buffer.as_span().take_front(order));
203 r_start_index = start;
204}
205
206void calculate_basis_cache(const int points_num,
207 const int evaluated_num,
208 const int8_t order,
209 const bool cyclic,
210 const Span<float> knots,
211 BasisCache &basis_cache)
212{
213 BLI_assert(points_num > 0);
214
215 const int8_t degree = order - 1;
216
217 basis_cache.weights.resize(evaluated_num * order);
218 basis_cache.start_indices.resize(evaluated_num);
219
220 if (evaluated_num == 0) {
221 return;
222 }
223
224 MutableSpan<float> basis_weights(basis_cache.weights);
225 MutableSpan<int> basis_start_indices(basis_cache.start_indices);
226
227 const int last_control_point_index = cyclic ? points_num + degree : points_num;
228 const int evaluated_segment_num = segments_num(evaluated_num, cyclic);
229
230 const float start = knots[degree];
231 const float end = knots[last_control_point_index];
232 const float step = (end - start) / evaluated_segment_num;
233 for (const int i : IndexRange(evaluated_num)) {
234 /* Clamp parameter due to floating point inaccuracy. */
235 const float parameter = std::clamp(start + step * i, knots[0], knots[points_num + degree]);
236
237 MutableSpan<float> point_weights = basis_weights.slice(i * order, order);
238
240 parameter, last_control_point_index, degree, knots, point_weights, basis_start_indices[i]);
241 }
242}
243
244template<typename T>
245static void interpolate_to_evaluated(const BasisCache &basis_cache,
246 const int8_t order,
247 const Span<T> src,
248 MutableSpan<T> dst)
249{
251
252 threading::parallel_for(dst.index_range(), 128, [&](const IndexRange range) {
253 for (const int i : range) {
254 Span<float> point_weights = basis_cache.weights.as_span().slice(i * order, order);
255 for (const int j : point_weights.index_range()) {
256 const int point_index = (basis_cache.start_indices[i] + j) % src.size();
257 mixer.mix_in(i, src[point_index], point_weights[j]);
258 }
259 }
260 mixer.finalize(range);
261 });
262}
263
264template<typename T>
265static void interpolate_to_evaluated_rational(const BasisCache &basis_cache,
266 const int8_t order,
267 const Span<float> control_weights,
268 const Span<T> src,
269 MutableSpan<T> dst)
270{
272
273 threading::parallel_for(dst.index_range(), 128, [&](const IndexRange range) {
274 for (const int i : range) {
275 Span<float> point_weights = basis_cache.weights.as_span().slice(i * order, order);
276
277 for (const int j : point_weights.index_range()) {
278 const int point_index = (basis_cache.start_indices[i] + j) % src.size();
279 const float weight = point_weights[j] * control_weights[point_index];
280 mixer.mix_in(i, src[point_index], weight);
281 }
282 }
283 mixer.finalize(range);
284 });
285}
286
287void interpolate_to_evaluated(const BasisCache &basis_cache,
288 const int8_t order,
289 const Span<float> control_weights,
290 const GSpan src,
291 GMutableSpan dst)
292{
293 if (basis_cache.invalid) {
294 dst.copy_from(src);
295 return;
296 }
297
298 BLI_assert(dst.size() == basis_cache.start_indices.size());
299 attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
300 using T = decltype(dummy);
301 if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
302 if (control_weights.is_empty()) {
303 interpolate_to_evaluated(basis_cache, order, src.typed<T>(), dst.typed<T>());
304 }
305 else {
306 interpolate_to_evaluated_rational(
307 basis_cache, order, control_weights, src.typed<T>(), dst.typed<T>());
308 }
309 }
310 });
311}
312
313} // namespace blender::bke::curves::nurbs
Low-level operations for curves.
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE int min_ii(int a, int b)
#define UNUSED_VARS_NDEBUG(...)
#define ELEM(...)
KnotsMode
@ NURBS_KNOT_MODE_ENDPOINT
@ NURBS_KNOT_MODE_BEZIER
@ NURBS_KNOT_MODE_ENDPOINT_BEZIER
@ NURBS_KNOT_MODE_CUSTOM
long long int int64_t
void resize(const int64_t new_size)
Span< T > as_span() const
Definition BLI_array.hh:232
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
void copy_from(GSpan values)
const CPPType & type() const
constexpr bool is_empty() 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 take_back(const int64_t n) const
Definition BLI_span.hh:640
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
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
int64_t size() const
void append(const T &value)
void resize(const int64_t new_size)
void reserve(const int64_t min_capacity)
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
typename DefaultMixerStruct< T >::type DefaultMixer
int calculate_evaluated_num(int points_num, int8_t order, bool cyclic, int resolution, KnotsMode knots_mode)
Vector< int > calculate_multiplicity_sequence(Span< float > knots)
bool check_valid_num_and_order(int points_num, int8_t order, bool cyclic, KnotsMode knots_mode)
void calculate_knots(int points_num, KnotsMode mode, int8_t order, bool cyclic, MutableSpan< float > knots)
void calculate_basis_cache(int points_num, int evaluated_num, int8_t order, bool cyclic, Span< float > knots, BasisCache &basis_cache)
static void calculate_basis_for_point(const float parameter, const int points_num, const int degree, const Span< float > knots, MutableSpan< float > r_weights, int &r_start_index)
int knots_num(int points_num, int8_t order, bool cyclic)
static void interpolate_to_evaluated_rational(const BasisCache &basis_cache, const int8_t order, const Span< float > control_weights, const Span< T > src, MutableSpan< T > dst)
void load_curve_knots(KnotsMode mode, int points_num, int8_t order, bool cyclic, IndexRange curve_knots, Span< float > custom_knots, MutableSpan< float > knots)
void copy_custom_knots(const bke::CurvesGeometry &src_curves, const IndexMask &exclude_curves, bke::CurvesGeometry &dst_curves)
void interpolate_to_evaluated(const BasisCache &basis_cache, int8_t order, Span< float > control_weights, GSpan src, GMutableSpan dst)
int segments_num(const int points_num, const bool cyclic)
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
i
Definition text_draw.cc:230