Blender V4.3
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
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
55void calculate_knots(const int points_num,
56 const KnotsMode mode,
57 const int8_t order,
58 const bool cyclic,
60{
61 BLI_assert(knots.size() == knots_num(points_num, order, cyclic));
62 UNUSED_VARS_NDEBUG(points_num);
63
64 const bool is_bezier = ELEM(mode, NURBS_KNOT_MODE_BEZIER, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
65 const bool is_end_point = ELEM(mode, NURBS_KNOT_MODE_ENDPOINT, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
66 /* Inner knots are always repeated once except on Bezier case. */
67 const int repeat_inner = is_bezier ? order - 1 : 1;
68 /* How many times to repeat 0.0 at the beginning of knot. */
69 const int head = is_end_point ? (order - (cyclic ? 1 : 0)) :
70 (is_bezier ? min_ii(2, repeat_inner) : 1);
71 /* Number of knots replicating widths of the starting knots.
72 * Covers both Cyclic and EndPoint cases. */
73 const int tail = cyclic ? 2 * order - 1 : (is_end_point ? order : 0);
74
75 int r = head;
76 float current = 0.0f;
77
78 const int offset = is_end_point && cyclic ? 1 : 0;
79 if (offset) {
80 knots[0] = current;
81 current += 1.0f;
82 }
83
84 for (const int i : IndexRange(offset, knots.size() - offset - tail)) {
85 knots[i] = current;
86 r--;
87 if (r == 0) {
88 current += 1.0;
89 r = repeat_inner;
90 }
91 }
92
93 const int tail_index = knots.size() - tail;
94 for (const int i : IndexRange(tail)) {
95 knots[tail_index + i] = current + (knots[i] - knots[0]);
96 }
97}
98
99static void calculate_basis_for_point(const float parameter,
100 const int points_num,
101 const int degree,
102 const Span<float> knots,
103 MutableSpan<float> r_weights,
104 int &r_start_index)
105{
106 const int order = degree + 1;
107
108 int start = 0;
109 int end = 0;
110 for (const int i : IndexRange(points_num + degree)) {
111 const bool knots_equal = knots[i] == knots[i + 1];
112 if (knots_equal || parameter < knots[i] || parameter > knots[i + 1]) {
113 continue;
114 }
115
116 start = std::max(i - degree, 0);
117 end = i;
118 break;
119 }
120
121 Array<float, 12> buffer(order * 2, 0.0f);
122
123 buffer[end - start] = 1.0f;
124
125 for (const int i_order : IndexRange(2, degree)) {
126 if (end + i_order >= knots.size()) {
127 end = points_num + degree - i_order;
128 }
129 for (const int i : IndexRange(end - start + 1)) {
130 const int knot_index = start + i;
131
132 float new_basis = 0.0f;
133 if (buffer[i] != 0.0f) {
134 new_basis += ((parameter - knots[knot_index]) * buffer[i]) /
135 (knots[knot_index + i_order - 1] - knots[knot_index]);
136 }
137
138 if (buffer[i + 1] != 0.0f) {
139 new_basis += ((knots[knot_index + i_order] - parameter) * buffer[i + 1]) /
140 (knots[knot_index + i_order] - knots[knot_index + 1]);
141 }
142
143 buffer[i] = new_basis;
144 }
145 }
146
147 buffer.as_mutable_span().drop_front(end - start + 1).fill(0.0f);
148 r_weights.copy_from(buffer.as_span().take_front(order));
149 r_start_index = start;
150}
151
152void calculate_basis_cache(const int points_num,
153 const int evaluated_num,
154 const int8_t order,
155 const bool cyclic,
156 const Span<float> knots,
157 BasisCache &basis_cache)
158{
159 BLI_assert(points_num > 0);
160
161 const int8_t degree = order - 1;
162
163 basis_cache.weights.resize(evaluated_num * order);
164 basis_cache.start_indices.resize(evaluated_num);
165
166 if (evaluated_num == 0) {
167 return;
168 }
169
170 MutableSpan<float> basis_weights(basis_cache.weights);
171 MutableSpan<int> basis_start_indices(basis_cache.start_indices);
172
173 const int last_control_point_index = cyclic ? points_num + degree : points_num;
174 const int evaluated_segment_num = segments_num(evaluated_num, cyclic);
175
176 const float start = knots[degree];
177 const float end = knots[last_control_point_index];
178 const float step = (end - start) / evaluated_segment_num;
179 for (const int i : IndexRange(evaluated_num)) {
180 /* Clamp parameter due to floating point inaccuracy. */
181 const float parameter = std::clamp(start + step * i, knots[0], knots[points_num + degree]);
182
183 MutableSpan<float> point_weights = basis_weights.slice(i * order, order);
184
186 parameter, last_control_point_index, degree, knots, point_weights, basis_start_indices[i]);
187 }
188}
189
190template<typename T>
191static void interpolate_to_evaluated(const BasisCache &basis_cache,
192 const int8_t order,
193 const Span<T> src,
194 MutableSpan<T> dst)
195{
197
198 threading::parallel_for(dst.index_range(), 128, [&](const IndexRange range) {
199 for (const int i : range) {
200 Span<float> point_weights = basis_cache.weights.as_span().slice(i * order, order);
201 for (const int j : point_weights.index_range()) {
202 const int point_index = (basis_cache.start_indices[i] + j) % src.size();
203 mixer.mix_in(i, src[point_index], point_weights[j]);
204 }
205 }
206 mixer.finalize(range);
207 });
208}
209
210template<typename T>
211static void interpolate_to_evaluated_rational(const BasisCache &basis_cache,
212 const int8_t order,
213 const Span<float> control_weights,
214 const Span<T> src,
215 MutableSpan<T> dst)
216{
218
219 threading::parallel_for(dst.index_range(), 128, [&](const IndexRange range) {
220 for (const int i : range) {
221 Span<float> point_weights = basis_cache.weights.as_span().slice(i * order, order);
222
223 for (const int j : point_weights.index_range()) {
224 const int point_index = (basis_cache.start_indices[i] + j) % src.size();
225 const float weight = point_weights[j] * control_weights[point_index];
226 mixer.mix_in(i, src[point_index], weight);
227 }
228 }
229 mixer.finalize(range);
230 });
231}
232
233void interpolate_to_evaluated(const BasisCache &basis_cache,
234 const int8_t order,
235 const Span<float> control_weights,
236 const GSpan src,
237 GMutableSpan dst)
238{
239 if (basis_cache.invalid) {
240 dst.copy_from(src);
241 return;
242 }
243
244 BLI_assert(dst.size() == basis_cache.start_indices.size());
245 attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
246 using T = decltype(dummy);
247 if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
248 if (control_weights.is_empty()) {
249 interpolate_to_evaluated(basis_cache, order, src.typed<T>(), dst.typed<T>());
250 }
251 else {
252 interpolate_to_evaluated_rational(
253 basis_cache, order, control_weights, src.typed<T>(), dst.typed<T>());
254 }
255 }
256 });
257}
258
259} // namespace blender::bke::curves::nurbs
Low-level operations for curves.
#define BLI_assert(a)
Definition BLI_assert.h:50
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
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 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 IndexRange index_range() const
Definition BLI_span.hh:671
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:726
constexpr int64_t size() const
Definition BLI_span.hh:253
int64_t size() const
void resize(const int64_t new_size)
typename DefaultMixerStruct< T >::type DefaultMixer
int calculate_evaluated_num(int points_num, int8_t order, bool cyclic, int resolution, KnotsMode knots_mode)
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 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:95
signed char int8_t
Definition stdint.h:75