Blender V5.0
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_eval_params(const int points_num,
17 const int8_t order,
18 const bool cyclic,
19 const KnotsMode knots_mode,
20 const int resolution)
21{
22 if (points_num < order) {
23 return false;
24 }
25
26 if (resolution < 1) {
27 return false;
28 }
29
31 if (knots_mode == NURBS_KNOT_MODE_BEZIER && points_num <= order) {
32 return false;
33 }
34 return (!cyclic || points_num % (order - 1) == 0);
35 }
36
37 return true;
38}
39
40static int calc_nonzero_knot_spans(const int points_num,
41 const KnotsMode mode,
42 const int8_t order,
43 const bool cyclic)
44{
45 const bool is_bezier = ELEM(mode, NURBS_KNOT_MODE_BEZIER, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
46 const bool is_end_point = ELEM(mode, NURBS_KNOT_MODE_ENDPOINT, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
47 /* Inner knots are always repeated once except on Bezier case. */
48 const int repeat_inner = is_bezier ? order - 1 : 1;
49 /* For non endpoint Bezier repeated knots are shifted by one. */
50 const int knots_before_geometry = order + int(is_bezier && !is_end_point && order > 2);
51 const int knots_after_geometry = order - 1 +
52 (cyclic && mode == NURBS_KNOT_MODE_ENDPOINT ? order - 2 : 0);
53
54 const int knots_total = knots_num(points_num, order, cyclic);
55 /* On these knots as parameters actual geometry is generated. */
56 const int geometry_knots = knots_total - knots_before_geometry - knots_after_geometry;
57 /* `repeat_inner - 1` is added to `ceil`. */
58 const int non_zero_knots = (geometry_knots + repeat_inner - 1) / repeat_inner;
59 return non_zero_knots;
60}
61
62static bool is_breakpoint(const Span<float> knots, const int knot_span)
63{
64 return (knots[knot_span + 1] - knots[knot_span]) > 0.0f;
65}
66
67static int count_nonzero_knot_spans(const int points_num,
68 const int order,
69 const bool cyclic,
70 const Span<float> knots)
71{
72 BLI_assert(points_num > 0);
73 const int degree = order - 1;
74 int span_num = 0;
75
76 const int wrapped_points_num = control_points_num(points_num, order, cyclic);
77 for (const int knot_span : IndexRange::from_begin_end(degree, wrapped_points_num)) {
78 span_num += is_breakpoint(knots, knot_span);
79 }
80 return span_num;
81}
82
83int calculate_evaluated_num(const int points_num,
84 const int8_t order,
85 const bool cyclic,
86 const int resolution,
87 const KnotsMode knots_mode,
88 const Span<float> knots)
89{
90 if (!check_valid_eval_params(points_num, order, cyclic, knots_mode, resolution)) {
91 return points_num;
92 }
93 const int nonzero_span_num = knots_mode == KnotsMode::NURBS_KNOT_MODE_CUSTOM &&
94 !knots.is_empty() ?
95 count_nonzero_knot_spans(points_num, order, cyclic, knots) :
96 calc_nonzero_knot_spans(points_num, knots_mode, order, cyclic);
97 return resolution * nonzero_span_num + int(!cyclic);
98}
99
100static void copy_custom_knots(const int8_t order,
101 const bool cyclic,
102 const Span<float> custom_knots,
103 MutableSpan<float> knots)
104{
105 knots.slice(0, custom_knots.size()).copy_from(custom_knots);
106 if (cyclic) {
107 const float last_knot = custom_knots.last();
108 const float shift = last_knot - knots[order - 1];
109 const MutableSpan<float> tail = knots.take_back(order - 1);
110 for (const int knot : tail.index_range()) {
111 tail[knot] = knots[order + knot] + shift;
112 }
113 }
114}
115
116void calculate_knots(const int points_num,
117 const KnotsMode mode,
118 const int8_t order,
119 const bool cyclic,
120 MutableSpan<float> knots)
121{
122 BLI_assert(knots.size() == knots_num(points_num, order, cyclic));
123 UNUSED_VARS_NDEBUG(points_num);
124
125 const bool is_bezier = ELEM(mode, NURBS_KNOT_MODE_BEZIER, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
126 const bool is_end_point = ELEM(mode, NURBS_KNOT_MODE_ENDPOINT, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
127 /* Inner knots are always repeated once except on Bezier case. */
128 const int repeat_inner = is_bezier ? order - 1 : 1;
129 /* How many times to repeat 0.0 at the beginning of knot. */
130 const int head = is_end_point ? (order - (cyclic ? 1 : 0)) :
131 (is_bezier ? min_ii(2, repeat_inner) : 1);
132 /* Number of knots replicating widths of the starting knots.
133 * Covers both Cyclic and EndPoint cases. */
134 const int tail = cyclic ? 2 * order - 1 : (is_end_point ? order : 0);
135
136 int r = head;
137 float current = 0.0f;
138
139 const int offset = is_end_point && cyclic ? 1 : 0;
140 if (offset) {
141 knots[0] = current;
142 current += 1.0f;
143 }
144
145 for (const int i : IndexRange(offset, knots.size() - offset - tail)) {
146 knots[i] = current;
147 r--;
148 if (r == 0) {
149 current += 1.0;
150 r = repeat_inner;
151 }
152 }
153
154 const int tail_index = knots.size() - tail;
155 for (const int i : IndexRange(tail)) {
156 knots[tail_index + i] = current + (knots[i] - knots[0]);
157 }
158}
159
161 const int points_num,
162 const int8_t order,
163 const bool cyclic,
164 const IndexRange curve_knots,
165 const Span<float> custom_knots,
166 MutableSpan<float> knots)
167{
168 if (mode == NURBS_KNOT_MODE_CUSTOM) {
169 BLI_assert(!custom_knots.is_empty());
170 BLI_assert(!curve_knots.is_empty());
171 copy_custom_knots(order, cyclic, custom_knots.slice(curve_knots), knots);
172 }
173 else {
174 calculate_knots(points_num, mode, order, cyclic, knots);
175 }
176}
177
179{
180 Vector<int> multiplicity;
181 multiplicity.reserve(knots.size());
182
183 int m = 1;
184 for (const int64_t i : knots.index_range().drop_front(1)) {
185 /* Only consider multiplicity for exact matching values. */
186 if (knots[i - 1] == knots[i]) {
187 m++;
188 }
189 else {
190 multiplicity.append(m);
191 m = 1;
192 }
193 }
194 multiplicity.append(m);
195 return multiplicity;
196}
197
198/* Basis function calculation, implementation based on 'The NURBS Book' p. 70, ISBN: 3540615458.
199 */
201 const int degree,
202 const float parameter,
203 const int span_index,
204 MutableSpan<float> r_weights,
205 int &r_start_index)
206{
207 BLI_assert(degree >= 1);
208 BLI_assert(span_index >= degree);
209 BLI_assert(span_index + degree < knots.size());
210 BLI_assert(knots[span_index + 1] > knots[span_index]);
211 const int order = degree + 1;
212
213 r_start_index = span_index - degree;
214
215 Array<float, 12> left(order);
216 Array<float, 12> right(order);
217 r_weights[0] = 1.0f;
218
219 for (const int j : IndexRange(1, degree)) {
220 left[j] = parameter - knots[span_index + 1 - j];
221 right[j] = knots[span_index + j] - parameter;
222 float saved = 0.0f;
223 for (const int r : IndexRange(j)) {
224 const float temp = r_weights[r] / (right[r + 1] + left[j - r]);
225 r_weights[r] = saved + right[r + 1] * temp;
226 saved = left[j - r] * temp;
227 }
228 r_weights[j] = saved;
229 }
230}
231
232void calculate_basis_cache(const int points_num,
233 const int evaluated_num,
234 const int8_t order,
235 const int resolution,
236 const bool cyclic,
237 const KnotsMode knots_mode,
238 const Span<float> knots,
239 BasisCache &basis_cache)
240{
241 BLI_assert(points_num > 0);
242
243 const int8_t degree = order - 1;
244 const int wrapped_points_num = control_points_num(points_num, order, cyclic);
245
246 basis_cache.weights.resize(evaluated_num * order);
247 basis_cache.start_indices.resize(evaluated_num);
248
249 if (evaluated_num == 0) {
250 return;
251 }
252
253 if (!check_valid_eval_params(points_num, order, cyclic, knots_mode, resolution)) {
254 return;
255 }
256
257 MutableSpan<float> basis_weights(basis_cache.weights);
258 MutableSpan<int> basis_start_indices(basis_cache.start_indices);
259
260 /* Find the 'span index' for each breakpoint that defines the 'evaluated spans'.
261 * An evaluated span (or 'segment') in this context is the parameter interval
262 * between two consecutive knots [i, i + 1], where the knot at index `i` is a
263 * breakpoint and is strictly less than the value of following knot. For repeated
264 * knots, with multiplicity > 1, only the rightmost is considered a breakpoint
265 * as the spans between repeated knot values are zero length!
266 */
267 const int breakpoint_num = (evaluated_num - !cyclic) / resolution;
268 Array<int, 20> span_offsets(breakpoint_num);
269
270 int breakpoint_count = 0;
271 for (const int span_index : IndexRange::from_begin_end(degree, wrapped_points_num)) {
272 if (is_breakpoint(knots, span_index)) {
273 span_offsets[breakpoint_count++] = span_index;
274 }
275 }
276 BLI_assert(breakpoint_count == breakpoint_num);
277
278 /* Build the basis cache, sampling each evaluated span at intervals. */
279 threading::parallel_for(span_offsets.index_range(), 4096, [&](const IndexRange range) {
280 for (const int index : range) {
281 const int span_index = span_offsets[index];
282 int eval_point = index * resolution;
283
284 const float knot_delta = knots[span_index + 1] - knots[span_index];
285 const float knot_step = knot_delta / resolution;
286 BLI_assert(knot_delta > 0.0f);
287
288 for (const int step : IndexRange::from_begin_size(0, resolution)) {
289 const float parameter = knots[span_index] + step * knot_step;
290 calculate_basis_for_point(knots,
291 degree,
292 parameter,
293 span_index,
294 basis_weights.slice(eval_point * order, order),
295 basis_start_indices[eval_point]);
296 eval_point++;
297 }
298 }
299 });
300 if (!cyclic) {
302 degree,
303 knots[wrapped_points_num],
304 span_offsets.last(),
305 basis_weights.slice(basis_weights.size() - order, order),
306 basis_start_indices.last());
307 }
308}
309
310template<typename T>
311static void interpolate_to_evaluated(const BasisCache &basis_cache,
312 const int8_t order,
313 const Span<T> src,
314 MutableSpan<T> dst)
315{
317
318 threading::parallel_for(dst.index_range(), 128, [&](const IndexRange range) {
319 for (const int i : range) {
320 Span<float> point_weights = basis_cache.weights.as_span().slice(i * order, order);
321 for (const int j : point_weights.index_range()) {
322 const int point_index = (basis_cache.start_indices[i] + j) % src.size();
323 mixer.mix_in(i, src[point_index], point_weights[j]);
324 }
325 }
326 mixer.finalize(range);
327 });
328}
329
330template<typename T>
331static void interpolate_to_evaluated_rational(const BasisCache &basis_cache,
332 const int8_t order,
333 const Span<float> control_weights,
334 const Span<T> src,
335 MutableSpan<T> dst)
336{
338
339 threading::parallel_for(dst.index_range(), 128, [&](const IndexRange range) {
340 for (const int i : range) {
341 Span<float> point_weights = basis_cache.weights.as_span().slice(i * order, order);
342
343 for (const int j : point_weights.index_range()) {
344 const int point_index = (basis_cache.start_indices[i] + j) % src.size();
345 const float weight = point_weights[j] * control_weights[point_index];
346 mixer.mix_in(i, src[point_index], weight);
347 }
348 }
349 mixer.finalize(range);
350 });
351}
352
353void interpolate_to_evaluated(const BasisCache &basis_cache,
354 const int8_t order,
355 const Span<float> control_weights,
356 const GSpan src,
357 GMutableSpan dst)
358{
359 if (basis_cache.invalid) {
360 dst.copy_from(src);
361 return;
362 }
363
364 BLI_assert(dst.size() == basis_cache.start_indices.size());
365 attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
366 using T = decltype(dummy);
367 if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
368 if (control_weights.is_empty()) {
369 interpolate_to_evaluated(basis_cache, order, src.typed<T>(), dst.typed<T>());
370 }
371 else {
372 interpolate_to_evaluated_rational(
373 basis_cache, order, control_weights, src.typed<T>(), dst.typed<T>());
374 }
375 }
376 });
377}
378
379} // 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)
IndexRange index_range() const
Definition BLI_array.hh:360
void copy_from(GSpan values)
const CPPType & type() const
constexpr bool is_empty() const
static constexpr IndexRange from_begin_end(const int64_t begin, const int64_t end)
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)
static int left
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
typename DefaultMixerStruct< T >::type DefaultMixer
Vector< int > calculate_multiplicity_sequence(Span< float > knots)
int calculate_evaluated_num(int points_num, int8_t order, bool cyclic, int resolution, KnotsMode knots_mode, Span< float > knots)
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, int resolution, bool cyclic, KnotsMode knots_mode, Span< float > knots, BasisCache &basis_cache)
static void calculate_basis_for_point(const Span< float > knots, const int degree, const float parameter, const int span_index, MutableSpan< float > r_weights, int &r_start_index)
bool check_valid_eval_params(int points_num, int8_t order, bool cyclic, KnotsMode knots_mode, int resolution)
int control_points_num(int num_control_points, int8_t order, bool cyclic)
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)
static bool is_breakpoint(const Span< float > knots, const int knot_span)
void load_curve_knots(KnotsMode mode, int points_num, int8_t order, bool cyclic, IndexRange curve_knots, Span< float > custom_knots, MutableSpan< float > knots)
static int count_nonzero_knot_spans(const int points_num, const int order, const bool cyclic, const Span< 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)
static int calc_nonzero_knot_spans(const int points_num, const KnotsMode mode, const int8_t order, 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