Blender V5.0
curves_geometry_test.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 "BKE_curves.hh"
10
11#include "testing/testing.h"
12
13namespace blender::bke::tests {
14
15static CurvesGeometry create_basic_curves(const int points_size, const int curves_size)
16{
17 CurvesGeometry curves(points_size, curves_size);
18
19 const int curve_length = points_size / curves_size;
20 for (const int i : curves.curves_range()) {
21 curves.offsets_for_write()[i] = curve_length * i;
22 }
23 curves.offsets_for_write().last() = points_size;
24
25 for (const int i : curves.points_range()) {
26 curves.positions_for_write()[i] = {float(i), float(i % curve_length), 0.0f};
27 }
28
29 return curves;
30}
31
32TEST(curves_geometry, Empty)
33{
34 CurvesGeometry empty(0, 0);
35 empty.cyclic();
36 EXPECT_TRUE(empty.is_empty());
37 EXPECT_FALSE(empty.bounds_min_max());
38}
39
40TEST(curves_geometry, Move)
41{
43
44 const int *offsets_data = curves.offsets().data();
45 const float3 *positions_data = curves.positions().data();
46
47 CurvesGeometry other = std::move(curves);
48
49 /* The old curves should be empty, and the offsets are expected to be null. */
50 EXPECT_TRUE(curves.is_empty()); /* NOLINT: bugprone-use-after-move */
51 EXPECT_EQ(curves.curve_offsets, nullptr); /* NOLINT: bugprone-use-after-move */
52
53 /* Just a basic check that the new curves work okay. */
54 EXPECT_TRUE(other.bounds_min_max());
55
56 curves = std::move(other);
57
58 CurvesGeometry second_other(std::move(curves));
59
60 /* The data should not have been reallocated ever. */
61 EXPECT_EQ(second_other.positions().data(), positions_data);
62 EXPECT_EQ(second_other.offsets().data(), offsets_data);
63}
64
65TEST(curves_geometry, TypeCount)
66{
68 curves.curve_types_for_write().copy_from({
79 });
80 curves.update_curve_types();
81 const std::array<int, CURVE_TYPES_NUM> &counts = curves.curve_type_counts();
83 EXPECT_EQ(counts[CURVE_TYPE_POLY], 3);
84 EXPECT_EQ(counts[CURVE_TYPE_BEZIER], 1);
85 EXPECT_EQ(counts[CURVE_TYPE_NURBS], 3);
86}
87
88TEST(curves_geometry, CyclicOffsets)
89{
91 {
92 EXPECT_FALSE(curves.has_cyclic_curve());
93 }
94 {
95 curves.cyclic_for_write().fill(true);
96 curves.tag_topology_changed();
97 EXPECT_TRUE(curves.has_cyclic_curve());
98 }
99 {
100 curves.cyclic_for_write().fill(false);
101 curves.tag_topology_changed();
102 EXPECT_FALSE(curves.has_cyclic_curve());
103 }
104 {
105 curves.attributes_for_write().remove("cyclic");
106 EXPECT_FALSE(curves.has_cyclic_curve());
107 }
108 {
109 curves.cyclic_for_write().copy_from(
110 {false, true, false, true, false, false, false, false, true, false});
111 curves.tag_topology_changed();
112 EXPECT_TRUE(curves.has_cyclic_curve());
113 }
114}
115
116TEST(curves_geometry, InvalidResolution)
117{
119 curves.curve_types_for_write().copy_from({
124 });
125 curves.update_curve_types();
126 curves.resolution_for_write().fill(0);
127
128 static const Array<int> expected_offsets{0, 10, 20, 30, 40};
129
130 OffsetIndices<int> actual_offsets = curves.evaluated_points_by_curve();
131 for (const int i : actual_offsets.index_range()) {
132 EXPECT_EQ(expected_offsets[i], actual_offsets.data()[i]);
133 }
134}
135
136TEST(curves_geometry, CatmullRomEvaluation)
137{
139 curves.fill_curve_types(CURVE_TYPE_CATMULL_ROM);
140 curves.resolution_for_write().fill(12);
141 curves.offsets_for_write().last() = 4;
142 curves.cyclic_for_write().fill(false);
143
144 MutableSpan<float3> positions = curves.positions_for_write();
145 positions[0] = {1, 1, 0};
146 positions[1] = {0, 1, 0};
147 positions[2] = {0, 0, 0};
148 positions[3] = {-1, 0, 0};
149
150 Span<float3> evaluated_positions = curves.evaluated_positions();
151 static const Array<float3> result_1{{
152 {1, 1, 0},
153 {0.948495, 1.00318, 0},
154 {0.87963, 1.01157, 0},
155 {0.796875, 1.02344, 0},
156 {0.703704, 1.03704, 0},
157 {0.603588, 1.05064, 0},
158 {0.5, 1.0625, 0},
159 {0.396412, 1.07089, 0},
160 {0.296296, 1.07407, 0},
161 {0.203125, 1.07031, 0},
162 {0.12037, 1.05787, 0},
163 {0.0515046, 1.03501, 0},
164 {0, 1, 0},
165 {-0.0318287, 0.948495, 0},
166 {-0.0462963, 0.87963, 0},
167 {-0.046875, 0.796875, 0},
168 {-0.037037, 0.703704, 0},
169 {-0.0202546, 0.603588, 0},
170 {0, 0.5, 0},
171 {0.0202546, 0.396412, 0},
172 {0.037037, 0.296296, 0},
173 {0.046875, 0.203125, 0},
174 {0.0462963, 0.12037, 0},
175 {0.0318287, 0.0515046, 0},
176 {0, 0, 0},
177 {-0.0515046, -0.0350116, 0},
178 {-0.12037, -0.0578704, 0},
179 {-0.203125, -0.0703125, 0},
180 {-0.296296, -0.0740741, 0},
181 {-0.396412, -0.0708912, 0},
182 {-0.5, -0.0625, 0},
183 {-0.603588, -0.0506366, 0},
184 {-0.703704, -0.037037, 0},
185 {-0.796875, -0.0234375, 0},
186 {-0.87963, -0.0115741, 0},
187 {-0.948495, -0.00318287, 0},
188 {-1, 0, 0},
189 }};
190 for (const int i : evaluated_positions.index_range()) {
191 EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f);
192 }
193
194 /* Changing the positions shouldn't cause the evaluated positions array to be reallocated. */
195 curves.tag_positions_changed();
196 curves.evaluated_positions();
197 EXPECT_EQ(curves.evaluated_positions().data(), evaluated_positions.data());
198
199 /* Call recalculation (which shouldn't happen because low-level accessors don't tag caches). */
200 EXPECT_EQ(evaluated_positions[12].x, 0.0f);
201 EXPECT_EQ(evaluated_positions[12].y, 1.0f);
202
203 positions[0] = {1, 0, 0};
204 positions[1] = {1, 1, 0};
205 positions[2] = {0, 1, 0};
206 positions[3] = {0, 0, 0};
207 curves.cyclic_for_write().fill(true);
208
209 /* Tag topology changed because the new cyclic value is different. */
210 curves.tag_topology_changed();
211
212 /* Retrieve the data again since the size should be larger than last time (one more segment). */
213 evaluated_positions = curves.evaluated_positions();
214 static const Array<float3> result_2{{
215 {1, 0, 0},
216 {1.03819, 0.0515046, 0},
217 {1.06944, 0.12037, 0},
218 {1.09375, 0.203125, 0},
219 {1.11111, 0.296296, 0},
220 {1.12153, 0.396412, 0},
221 {1.125, 0.5, 0},
222 {1.12153, 0.603588, 0},
223 {1.11111, 0.703704, 0},
224 {1.09375, 0.796875, 0},
225 {1.06944, 0.87963, 0},
226 {1.03819, 0.948495, 0},
227 {1, 1, 0},
228 {0.948495, 1.03819, 0},
229 {0.87963, 1.06944, 0},
230 {0.796875, 1.09375, 0},
231 {0.703704, 1.11111, 0},
232 {0.603588, 1.12153, 0},
233 {0.5, 1.125, 0},
234 {0.396412, 1.12153, 0},
235 {0.296296, 1.11111, 0},
236 {0.203125, 1.09375, 0},
237 {0.12037, 1.06944, 0},
238 {0.0515046, 1.03819, 0},
239 {0, 1, 0},
240 {-0.0381944, 0.948495, 0},
241 {-0.0694444, 0.87963, 0},
242 {-0.09375, 0.796875, 0},
243 {-0.111111, 0.703704, 0},
244 {-0.121528, 0.603588, 0},
245 {-0.125, 0.5, 0},
246 {-0.121528, 0.396412, 0},
247 {-0.111111, 0.296296, 0},
248 {-0.09375, 0.203125, 0},
249 {-0.0694444, 0.12037, 0},
250 {-0.0381944, 0.0515046, 0},
251 {0, 0, 0},
252 {0.0515046, -0.0381944, 0},
253 {0.12037, -0.0694444, 0},
254 {0.203125, -0.09375, 0},
255 {0.296296, -0.111111, 0},
256 {0.396412, -0.121528, 0},
257 {0.5, -0.125, 0},
258 {0.603588, -0.121528, 0},
259 {0.703704, -0.111111, 0},
260 {0.796875, -0.09375, 0},
261 {0.87963, -0.0694444, 0},
262 {0.948495, -0.0381944, 0},
263 }};
264 for (const int i : evaluated_positions.index_range()) {
265 EXPECT_V3_NEAR(evaluated_positions[i], result_2[i], 1e-5f);
266 }
267}
268
269TEST(curves_geometry, CatmullRomTwoPointCyclic)
270{
272 curves.fill_curve_types(CURVE_TYPE_CATMULL_ROM);
273 curves.resolution_for_write().fill(12);
274 curves.offsets_for_write().last() = 2;
275 curves.cyclic_for_write().fill(true);
276
277 /* The curve should still be cyclic when there are only two control points. */
278 EXPECT_EQ(curves.evaluated_points_num(), 24);
279}
280
281TEST(curves_geometry, BezierPositionEvaluation)
282{
284 curves.fill_curve_types(CURVE_TYPE_BEZIER);
285 curves.resolution_for_write().fill(12);
286 curves.offsets_for_write().last() = 2;
287
288 MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
289 MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
290 MutableSpan<float3> positions = curves.positions_for_write();
291 positions.first() = {-1, 0, 0};
292 positions.last() = {1, 0, 0};
293 handles_right.first() = {-0.5f, 0.5f, 0.0f};
294 handles_left.last() = {0, 0, 0};
295
296 /* Dangling handles shouldn't be used in a non-cyclic curve. */
297 handles_left.first() = {100, 100, 100};
298 handles_right.last() = {100, 100, 100};
299
300 Span<float3> evaluated_positions = curves.evaluated_positions();
301 static const Array<float3> result_1{{
302 {-1, 0, 0},
303 {-0.874711, 0.105035, 0},
304 {-0.747685, 0.173611, 0},
305 {-0.617188, 0.210937, 0},
306 {-0.481481, 0.222222, 0},
307 {-0.338831, 0.212674, 0},
308 {-0.1875, 0.1875, 0},
309 {-0.0257524, 0.15191, 0},
310 {0.148148, 0.111111, 0},
311 {0.335937, 0.0703125, 0},
312 {0.539352, 0.0347222, 0},
313 {0.760127, 0.00954859, 0},
314 {1, 0, 0},
315 }};
316 for (const int i : evaluated_positions.index_range()) {
317 EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f);
318 }
319
320 curves.resize(4, 2);
321 curves.fill_curve_types(CURVE_TYPE_BEZIER);
322 curves.resolution_for_write().fill(9);
323 curves.offsets_for_write().last() = 4;
324 handles_left = curves.handle_positions_left_for_write();
325 handles_right = curves.handle_positions_right_for_write();
326 positions = curves.positions_for_write();
327 positions[2] = {-1, 1, 0};
328 positions[3] = {1, 1, 0};
329 handles_right[2] = {-0.5f, 1.5f, 0.0f};
330 handles_left[3] = {0, 1, 0};
331
332 /* Dangling handles shouldn't be used in a non-cyclic curve. */
333 handles_left[2] = {-100, -100, -100};
334 handles_right[3] = {-100, -100, -100};
335
336 evaluated_positions = curves.evaluated_positions();
337 EXPECT_EQ(evaluated_positions.size(), 20);
338 static const Array<float3> result_2{{
339 {-1, 0, 0},
340 {-0.832647, 0.131687, 0},
341 {-0.66118, 0.201646, 0},
342 {-0.481481, 0.222222, 0},
343 {-0.289438, 0.205761, 0},
344 {-0.0809327, 0.164609, 0},
345 {0.148148, 0.111111, 0},
346 {0.40192, 0.0576133, 0},
347 {0.684499, 0.016461, 0},
348 {1, 0, 0},
349 {-1, 1, 0},
350 {-0.832647, 1.13169, 0},
351 {-0.66118, 1.20165, 0},
352 {-0.481481, 1.22222, 0},
353 {-0.289438, 1.20576, 0},
354 {-0.0809327, 1.16461, 0},
355 {0.148148, 1.11111, 0},
356 {0.40192, 1.05761, 0},
357 {0.684499, 1.01646, 0},
358 {1, 1, 0},
359 }};
360 for (const int i : evaluated_positions.index_range()) {
361 EXPECT_V3_NEAR(evaluated_positions[i], result_2[i], 1e-5f);
362 }
363}
364
365TEST(curves_geometry, NURBSEvaluation)
366{
368 curves.fill_curve_types(CURVE_TYPE_NURBS);
369 curves.resolution_for_write().fill(10);
370 curves.offsets_for_write().last() = 4;
371
372 MutableSpan<float3> positions = curves.positions_for_write();
373 positions[0] = {1, 1, 0};
374 positions[1] = {0, 1, 0};
375 positions[2] = {0, 0, 0};
376 positions[3] = {-1, 0, 0};
377
378 Span<float3> evaluated_positions = curves.evaluated_positions();
379 static const Array<float3> result_1{{
380 {0.166667, 0.833333, 0},
381 {0.121333, 0.778667, 0},
382 {0.084, 0.716, 0},
383 {0.0526667, 0.647333, 0},
384 {0.0253333, 0.574667, 0},
385 {0, 0.5, 0},
386 {-0.0253333, 0.425333, 0},
387 {-0.0526667, 0.352667, 0},
388 {-0.084, 0.284, 0},
389 {-0.121333, 0.221333, 0},
390 {-0.166667, 0.166667, 0},
391 }};
392 for (const int i : evaluated_positions.index_range()) {
393 EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f);
394 }
395
396 /* Test a cyclic curve. */
397 curves.cyclic_for_write().fill(true);
398 curves.tag_topology_changed();
399 evaluated_positions = curves.evaluated_positions();
400 static const Array<float3> result_2{{
401 {0.166667, 0.833333, 0}, {0.121333, 0.778667, 0},
402 {0.084, 0.716, 0}, {0.0526667, 0.647333, 0},
403 {0.0253333, 0.574667, 0}, {0, 0.5, 0},
404 {-0.0253333, 0.425333, 0}, {-0.0526667, 0.352667, 0},
405 {-0.084, 0.284, 0}, {-0.121333, 0.221333, 0},
406 {-0.166667, 0.166667, 0}, {-0.221, 0.121667, 0},
407 {-0.281333, 0.0866667, 0}, {-0.343667, 0.0616666, 0},
408 {-0.404, 0.0466667, 0}, {-0.458333, 0.0416667, 0},
409 {-0.502667, 0.0466667, 0}, {-0.533, 0.0616666, 0},
410 {-0.545333, 0.0866667, 0}, {-0.535667, 0.121667, 0},
411 {-0.5, 0.166667, 0}, {-0.436, 0.221334, 0},
412 {-0.348, 0.284, 0}, {-0.242, 0.352667, 0},
413 {-0.124, 0.425333, 0}, {0, 0.5, 0},
414 {0.124, 0.574667, 0}, {0.242, 0.647333, 0},
415 {0.348, 0.716, 0}, {0.436, 0.778667, 0},
416 {0.5, 0.833333, 0}, {0.535667, 0.878334, 0},
417 {0.545333, 0.913333, 0}, {0.533, 0.938333, 0},
418 {0.502667, 0.953333, 0}, {0.458333, 0.958333, 0},
419 {0.404, 0.953333, 0}, {0.343667, 0.938333, 0},
420 {0.281333, 0.913333, 0}, {0.221, 0.878333, 0},
421 }};
422 for (const int i : evaluated_positions.index_range()) {
423 EXPECT_V3_NEAR(evaluated_positions[i], result_2[i], 1e-5f);
424 }
425
426 /* Test a circular cyclic curve with weights. */
427 positions[0] = {1, 0, 0};
428 positions[1] = {1, 1, 0};
429 positions[2] = {0, 1, 0};
430 positions[3] = {0, 0, 0};
431 curves.nurbs_weights_for_write().fill(1.0f);
432 curves.nurbs_weights_for_write()[0] = 4.0f;
433 curves.tag_positions_changed();
434 static const Array<float3> result_3{{
435 {0.888889, 0.555556, 0}, {0.837792, 0.643703, 0}, {0.773885, 0.727176, 0},
436 {0.698961, 0.800967, 0}, {0.616125, 0.860409, 0}, {0.529412, 0.901961, 0},
437 {0.443152, 0.923773, 0}, {0.361289, 0.925835, 0}, {0.286853, 0.909695, 0},
438 {0.221722, 0.877894, 0}, {0.166667, 0.833333, 0}, {0.122106, 0.778278, 0},
439 {0.0903055, 0.713148, 0}, {0.0741654, 0.638711, 0}, {0.0762274, 0.556847, 0},
440 {0.0980392, 0.470588, 0}, {0.139591, 0.383875, 0}, {0.199032, 0.301039, 0},
441 {0.272824, 0.226114, 0}, {0.356297, 0.162208, 0}, {0.444444, 0.111111, 0},
442 {0.531911, 0.0731388, 0}, {0.612554, 0.0468976, 0}, {0.683378, 0.0301622, 0},
443 {0.74391, 0.0207962, 0}, {0.794872, 0.017094, 0}, {0.837411, 0.017839, 0},
444 {0.872706, 0.0222583, 0}, {0.901798, 0.0299677, 0}, {0.925515, 0.0409445, 0},
445 {0.944444, 0.0555556, 0}, {0.959056, 0.0744855, 0}, {0.970032, 0.0982019, 0},
446 {0.977742, 0.127294, 0}, {0.982161, 0.162589, 0}, {0.982906, 0.205128, 0},
447 {0.979204, 0.256091, 0}, {0.969838, 0.316622, 0}, {0.953102, 0.387446, 0},
448 {0.926861, 0.468089, 0},
449 }};
450 evaluated_positions = curves.evaluated_positions();
451 for (const int i : evaluated_positions.index_range()) {
452 EXPECT_V3_NEAR(evaluated_positions[i], result_3[i], 1e-5f);
453 }
454}
455
456TEST(curves_geometry, BezierGenericEvaluation)
457{
459 curves.fill_curve_types(CURVE_TYPE_BEZIER);
460 curves.resolution_for_write().fill(8);
461 curves.offsets_for_write().last() = 3;
462
463 MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
464 MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
465 MutableSpan<float3> positions = curves.positions_for_write();
466 positions.first() = {-1, 0, 0};
467 handles_right.first() = {-1, 1, 0};
468 handles_left[1] = {0, 0, 0};
469 positions[1] = {1, 0, 0};
470 handles_right[1] = {2, 0, 0};
471 handles_left.last() = {1, 1, 0};
472 positions.last() = {2, 1, 0};
473
474 /* Dangling handles shouldn't be used in a non-cyclic curve. */
475 handles_left.first() = {100, 100, 100};
476 handles_right.last() = {100, 100, 100};
477
478 Span<float3> evaluated_positions = curves.evaluated_positions();
479 static const Array<float3> result_1{{
480 {-1.0f, 0.0f, 0.0f},
481 {-0.955078f, 0.287109f, 0.0f},
482 {-0.828125f, 0.421875f, 0.0f},
483 {-0.630859f, 0.439453f, 0.0f},
484 {-0.375f, 0.375f, 0.0f},
485 {-0.0722656f, 0.263672f, 0.0f},
486 {0.265625f, 0.140625f, 0.0f},
487 {0.626953f, 0.0410156f, 0.0f},
488 {1.0f, 0.0f, 0.0f},
489 {1.28906f, 0.0429688f, 0.0f},
490 {1.4375f, 0.15625f, 0.0f},
491 {1.49219f, 0.316406f, 0.0f},
492 {1.5f, 0.5f, 0.0f},
493 {1.50781f, 0.683594f, 0.0f},
494 {1.5625f, 0.84375f, 0.0f},
495 {1.71094f, 0.957031f, 0.0f},
496 {2.0f, 1.0f, 0.0f},
497 }};
498 for (const int i : evaluated_positions.index_range()) {
499 EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f);
500 }
501
502 Array<float> radii{{0.0f, 1.0f, 2.0f}};
503 Array<float> evaluated_radii(17);
504 curves.interpolate_to_evaluated(0, radii.as_span(), evaluated_radii.as_mutable_span());
505 static const Array<float> result_2{{
506 0.0f,
507 0.125f,
508 0.25f,
509 0.375f,
510 0.5f,
511 0.625f,
512 0.75f,
513 0.875f,
514 1.0f,
515 1.125f,
516 1.25f,
517 1.375f,
518 1.5f,
519 1.625f,
520 1.75f,
521 1.875f,
522 2.0f,
523 }};
524 for (const int i : evaluated_radii.index_range()) {
525 EXPECT_NEAR(evaluated_radii[i], result_2[i], 1e-6f);
526 }
527}
528
529/* -------------------------------------------------------------------- */
532
533TEST(curves_geometry, BasisCacheBezierSegmentDeg2)
534{
535 const int order = 3;
536 const int point_count = 3;
537 const int resolution = 3;
538 const bool is_cyclic = false;
539
540 const std::array<float, 6> knots_data{0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f};
541 const Span<float> knots = Span<float>(knots_data);
542
543 /* Expectation */
544 auto fn_Ni2_span = [](MutableSpan<float> Ni2, const float u) {
545 const float nu = 1.0f - u;
546 Ni2[0] = nu * nu;
547 Ni2[1] = 2.0f * u * nu;
548 Ni2[2] = u * u;
549 };
550
551 std::array<float, 12> expected_data;
552 MutableSpan<float> expectation = MutableSpan<float>(expected_data);
553 fn_Ni2_span(expectation.slice(0, 3), 0.0f);
554 fn_Ni2_span(expectation.slice(3, 3), 1.0f / 3.0f);
555 fn_Ni2_span(expectation.slice(6, 3), 2.0f / 3.0f);
556 fn_Ni2_span(expectation.slice(9, 3), 1.0f);
557
558 /* Test */
559 const int evaluated_num = curves::nurbs::calculate_evaluated_num(
560 point_count, order, is_cyclic, resolution, KnotsMode::NURBS_KNOT_MODE_CUSTOM, knots);
561 EXPECT_EQ(evaluated_num, resolution + 1);
562
565 evaluated_num,
566 order,
567 resolution,
568 is_cyclic,
569 KnotsMode::NURBS_KNOT_MODE_CUSTOM,
570 knots,
571 cache);
572 EXPECT_EQ_SPAN<float>(expectation, cache.weights);
573}
574
575TEST(curves_geometry, BasisCacheNonUniformDeg2)
576{
577 const int order = 3;
578 const int point_count = 8;
579 const int resolution = 3;
580 const bool is_cyclic = false;
581
582 const std::array<float, 11> knots_data{
583 0.0f, 0.0f, 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 5.0f};
584 const Span<float> knots = Span<float>(knots_data);
585
586 /* Expectation */
587 auto fn_Ni2_span0 = [](MutableSpan<float> Ni2, const float u) {
588 Ni2[0] = square_f(1.0f - u);
589 Ni2[1] = 2.0f * u - 1.5f * square_f(u);
590 Ni2[2] = square_f(u) / 2.0f;
591 };
592 auto fn_Ni2_span1 = [](MutableSpan<float> Ni2, float u) {
593 Ni2[0] = square_f(2.0f - u) / 2.0f;
594 Ni2[1] = -1.5f + 3 * u - square_f(u);
595 Ni2[2] = square_f(u - 1.0f) / 2.0f;
596 };
597 auto fn_Ni2_span2 = [](MutableSpan<float> Ni2, float u) {
598 Ni2[0] = square_f(3.0f - u) / 2.0f;
599 Ni2[1] = -5.5f + 5.0f * u - square_f(u);
600 Ni2[2] = square_f(u - 2.0f) / 2.0f;
601 };
602 auto fn_Ni2_span3 = [](MutableSpan<float> Ni2, float u) {
603 Ni2[0] = square_f(4.0f - u) / 2.0f;
604 Ni2[1] = -16.0f + 10.0f * u - 1.5f * square_f(u);
605 Ni2[2] = square_f(u - 3.0f);
606 };
607 auto fn_Ni2_span4 = [](MutableSpan<float> Ni2, float u) {
608 Ni2[0] = square_f(5.0f - u);
609 Ni2[1] = 2.0f * (u - 4.0f) * (5.0f - u);
610 Ni2[2] = square_f(u - 4.0f);
611 };
612
613 std::array<float, 48> expected_data;
614 MutableSpan<float> expectation = MutableSpan<float>(expected_data);
615 for (int i = 0; i < 3; i++) {
616 const float du = i / 3.0f;
617 const int step = i * 3;
618 fn_Ni2_span0(expectation.slice(step, 3), du);
619 fn_Ni2_span1(expectation.slice(step + 9, 3), 1.0f + du);
620 fn_Ni2_span2(expectation.slice(step + 18, 3), 2.0f + du);
621 fn_Ni2_span3(expectation.slice(step + 27, 3), 3.0f + du);
622 fn_Ni2_span4(expectation.slice(step + 36, 3), 4.0f + du);
623 }
624 fn_Ni2_span4(expectation.slice(45, 3), 5.0f);
625
626 /* Test */
627 const int evaluated_num = curves::nurbs::calculate_evaluated_num(
628 point_count, order, is_cyclic, resolution, KnotsMode::NURBS_KNOT_MODE_CUSTOM, knots);
629 EXPECT_EQ(evaluated_num, 5 * resolution + 1);
630
633 evaluated_num,
634 order,
635 resolution,
636 is_cyclic,
637 KnotsMode::NURBS_KNOT_MODE_CUSTOM,
638 knots,
639 cache);
640 EXPECT_NEAR_SPAN<float>(expectation, cache.weights, 1e-6f);
641}
642
644
645TEST(knot_vector, KnotVectorUniform)
646{
647 constexpr int8_t order = 5;
648 constexpr int points_num = 7;
649
650 Vector<float> knots(curves::nurbs::knots_num(points_num, order, false));
652 points_num, KnotsMode::NURBS_KNOT_MODE_NORMAL, order, false, knots);
653
655 EXPECT_EQ_SPAN<int>(Span({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}), multiplicity);
656}
657
658TEST(knot_vector, KnotVectorUniformClamped)
659{
660 constexpr int8_t order = 3;
661 constexpr int points_num = 7;
662
663 Vector<float> knots(curves::nurbs::knots_num(points_num, order, false));
665 points_num, KnotsMode::NURBS_KNOT_MODE_ENDPOINT, order, false, knots);
666
668 EXPECT_EQ_SPAN<int>(Span({3, 1, 1, 1, 1, 3}), multiplicity);
669}
670
671/* -------------------------------------------------------------------- */
674
675TEST(knot_vector, KnotVectorBezierClampedSegmentDeg2)
676{
677 constexpr int8_t order = 3;
678 constexpr int points_num = 3;
679
680 Vector<float> knots(curves::nurbs::knots_num(points_num, order, false));
682 points_num, KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER, order, false, knots);
683
685 EXPECT_EQ_SPAN<int>(Span({3, 3}), multiplicity);
686}
687
688TEST(knot_vector, KnotVectorBezierClampedSegmentDeg4)
689{
690 constexpr int8_t order = 5;
691 constexpr int points_num = 5;
692
693 Vector<float> knots(curves::nurbs::knots_num(points_num, order, false));
695 points_num, KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER, order, false, knots);
696
698 EXPECT_EQ_SPAN<int>(Span({5, 5}), multiplicity);
699}
700
701TEST(knot_vector, KnotVectorBezierClampedDeg2)
702{
703 constexpr int8_t order = 3;
704 constexpr int points_num = 9;
705
706 Vector<float> knots(curves::nurbs::knots_num(points_num, order, false));
708 points_num, KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER, order, false, knots);
709
711 EXPECT_EQ_SPAN<int>(Span({3, 2, 2, 2, 3}), multiplicity);
712}
713
714TEST(knot_vector, KnotVectorBezierClampedUnevenDeg2)
715{
716 constexpr int8_t order = 3;
717 constexpr int points_num = 8;
718
719 Vector<float> knots(curves::nurbs::knots_num(points_num, order, false));
721 points_num, KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER, order, false, knots);
722
724 EXPECT_EQ_SPAN<int>(Span({3, 2, 2, 4}), multiplicity);
725}
726
727TEST(knot_vector, KnotVectorBezierClampedDeg4)
728{
729 constexpr int8_t order = 5;
730 constexpr int points_num = 13;
731
732 Vector<float> knots(curves::nurbs::knots_num(points_num, order, false));
734 points_num, KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER, order, false, knots);
735
737 EXPECT_EQ_SPAN<int>(Span({5, 4, 4, 5}), multiplicity);
738}
739
740TEST(knot_vector, KnotVectorBezierClampedUnevenDeg4)
741{
742 constexpr int8_t order = 5;
743 constexpr int points_num[4] = {12, 11, 10, 9};
744 const std::array<std::array<int, 3>, 4> expectation = {std::array<int, 3>{5, 4, 8},
745 std::array<int, 3>{5, 4, 7},
746 std::array<int, 3>{5, 4, 6},
747 std::array<int, 3>{5, 4, 5}};
748
749 for (int i = 0; i < expectation.size(); i++) {
750 Vector<float> knots(curves::nurbs::knots_num(points_num[i], order, false));
752 points_num[i], KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER, order, false, knots);
753
755 EXPECT_EQ_SPAN<int>(Span(expectation[i]), multiplicity);
756 }
757}
758
759TEST(knot_vector, KnotVectorCircleCyclicUnevenDeg2)
760{
761 constexpr int8_t order = 3;
762 constexpr int points_num = 8;
763
764 Vector<float> knots(curves::nurbs::knots_num(points_num, order, true));
766 points_num, KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER, order, true, knots);
767
769 EXPECT_EQ_SPAN<int>(Span({1, 2, 2, 2, 2, 2, 2}), multiplicity);
770}
771
772TEST(knot_vector, KnotVectorBezierClampedCyclicUnevenDeg4)
773{
774 constexpr int8_t order = 5;
775 constexpr int points_num[4] = {12, 11, 10, 9};
776 const std::array<std::array<int, 6>, 4> expectation = {std::array<int, 6>{1, 4, 4, 4, 4, 4},
777 std::array<int, 6>{1, 4, 4, 3, 4, 4},
778 std::array<int, 6>{1, 4, 4, 2, 4, 4},
779 std::array<int, 6>{1, 4, 4, 1, 4, 4}};
780
781 for (int i = 0; i < expectation.size(); i++) {
782 Vector<float> knots(curves::nurbs::knots_num(points_num[i], order, true));
784 points_num[i], KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER, order, true, knots);
785
787 EXPECT_EQ_SPAN<int>(Span(expectation[i]), multiplicity);
788 }
789}
790
792
793/* -------------------------------------------------------------------- */
796
797TEST(knot_vector, KnotVectorBezierSegmentDeg2)
798{
799 constexpr int8_t order = 4;
800 constexpr int points_num = 4;
801
802 Vector<float> knots(curves::nurbs::knots_num(points_num, order, false));
804 points_num, KnotsMode::NURBS_KNOT_MODE_BEZIER, order, false, knots);
805
807 EXPECT_EQ_SPAN<int>(Span({2, 3, 3}), multiplicity);
808}
809
810TEST(knot_vector, KnotVectorBezierUnevenDeg2)
811{
812 constexpr int8_t order = 3;
813 constexpr int points_num[4] = {8, 7, 6, 5};
814 const std::array<std::array<int, 6>, 4> expectation = {std::array<int, 6>{2, 2, 2, 2, 2, 1},
815 std::array<int, 6>{2, 2, 2, 2, 2, -1},
816 std::array<int, 6>{2, 2, 2, 2, 1, -1},
817 std::array<int, 6>{2, 2, 2, 2, -1, -1}};
818
819 for (int i = 0; i < expectation.size(); i++) {
820 Vector<float> knots(curves::nurbs::knots_num(points_num[i], order, false));
822 points_num[i], KnotsMode::NURBS_KNOT_MODE_BEZIER, order, false, knots);
823
825 EXPECT_EQ_SPAN<int>(Span(expectation[i].data(), multiplicity.size()), multiplicity);
826 }
827}
828
829TEST(knot_vector, KnotVectorBezierUnevenDeg4)
830{
831 constexpr int8_t order = 5;
832 constexpr int points_num[6] = {14, 13, 12, 11, 10, 9};
833 const std::array<std::array<int, 6>, 6> expectation = {std::array<int, 6>{2, 4, 4, 4, 4, 1},
834 std::array<int, 6>{2, 4, 4, 4, 4, -1},
835 std::array<int, 6>{2, 4, 4, 4, 3, -1},
836 std::array<int, 6>{2, 4, 4, 4, 2, -1},
837 std::array<int, 6>{2, 4, 4, 4, 1, -1},
838 std::array<int, 6>{2, 4, 4, 4, -1, -1}};
839
840 for (int i = 0; i < expectation.size(); i++) {
841 Vector<float> knots(curves::nurbs::knots_num(points_num[i], order, false));
843 points_num[i], KnotsMode::NURBS_KNOT_MODE_BEZIER, order, false, knots);
844
846 EXPECT_EQ_SPAN<int>(Span(expectation[i].data(), multiplicity.size()), multiplicity);
847 }
848}
849
850TEST(knot_vector, KnotVectorBezierCyclicUnevenDeg4)
851{
852 constexpr int8_t order = 5;
853 constexpr int points_num[4] = {12, 11, 10, 9};
854 const std::array<std::array<int, 6>, 4> expectation = {std::array<int, 6>{2, 4, 4, 4, 4, 3},
855 std::array<int, 6>{2, 4, 4, 3, 4, 3},
856 std::array<int, 6>{2, 4, 4, 2, 4, 3},
857 std::array<int, 6>{2, 4, 5, 4, 3, -1}};
858
859 for (int i = 0; i < expectation.size(); i++) {
860 Vector<float> knots(curves::nurbs::knots_num(points_num[i], order, true));
862 points_num[i], KnotsMode::NURBS_KNOT_MODE_BEZIER, order, true, knots);
863
865 EXPECT_EQ_SPAN<int>(Span(expectation[i].data(), multiplicity.size()), multiplicity);
866 }
867}
868
870
871} // namespace blender::bke::tests
Low-level operations for curves.
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
MINLINE float square_f(float a)
BMesh const char void * data
constexpr const T * data() const
Definition BLI_span.hh:215
Span< T > as_span() const
Definition BLI_array.hh:243
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:248
IndexRange index_range() const
Definition BLI_array.hh:360
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 T & last(const int64_t n=0) const
Definition BLI_span.hh:689
constexpr const T * data() const
Definition BLI_span.hh:215
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
int64_t size() const
Span< float3 > positions() const
std::optional< Bounds< float3 > > bounds_min_max(bool use_radius=true) const
VArray< bool > cyclic() const
nullptr float
static bool is_cyclic(const Nurb *nu)
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
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)
int knots_num(int points_num, int8_t order, bool cyclic)
TEST(action_groups, ReconstructGroupsWithReordering)
static CurvesGeometry create_basic_curves(const int points_size, const int curves_size)
VecBase< float, 3 > float3
i
Definition text_draw.cc:230