Blender  V3.3
curves_geometry_test.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
7 #include "BKE_curves.hh"
8 
9 #include "testing/testing.h"
10 
11 namespace blender::bke::tests {
12 
13 static CurvesGeometry create_basic_curves(const int points_size, const int curves_size)
14 {
15  CurvesGeometry curves(points_size, curves_size);
16 
17  const int curve_length = points_size / curves_size;
18  for (const int i : curves.curves_range()) {
19  curves.offsets_for_write()[i] = curve_length * i;
20  }
21  curves.offsets_for_write().last() = points_size;
22 
23  for (const int i : curves.points_range()) {
24  curves.positions_for_write()[i] = {float(i), float(i % curve_length), 0.0f};
25  }
26 
27  return curves;
28 }
29 
30 TEST(curves_geometry, Empty)
31 {
32  CurvesGeometry empty(0, 0);
33  empty.cyclic();
34  float3 min;
35  float3 max;
36  EXPECT_FALSE(empty.bounds_min_max(min, max));
37 }
38 
39 TEST(curves_geometry, Move)
40 {
42 
43  const int *offsets_data = curves.offsets().data();
44  const float3 *positions_data = curves.positions().data();
45 
46  CurvesGeometry other = std::move(curves);
47 
48  /* The old curves should be empty, and the offsets are expected to be null. */
49  EXPECT_EQ(curves.points_num(), 0); /* NOLINT: bugprone-use-after-move */
50  EXPECT_EQ(curves.curve_offsets, nullptr); /* NOLINT: bugprone-use-after-move */
51 
52  /* Just a basic check that the new curves work okay. */
53  float3 min;
54  float3 max;
55  EXPECT_TRUE(other.bounds_min_max(min, max));
56 
57  curves = std::move(other);
58 
59  CurvesGeometry second_other(std::move(curves));
60 
61  /* The data should not have been reallocated ever. */
62  EXPECT_EQ(second_other.positions().data(), positions_data);
63  EXPECT_EQ(second_other.offsets().data(), offsets_data);
64 }
65 
66 TEST(curves_geometry, TypeCount)
67 {
69  curves.curve_types_for_write().copy_from({
80  });
81  curves.update_curve_types();
82  const std::array<int, CURVE_TYPES_NUM> &counts = curves.curve_type_counts();
84  EXPECT_EQ(counts[CURVE_TYPE_POLY], 3);
85  EXPECT_EQ(counts[CURVE_TYPE_BEZIER], 1);
86  EXPECT_EQ(counts[CURVE_TYPE_NURBS], 3);
87 }
88 
89 TEST(curves_geometry, CatmullRomEvaluation)
90 {
91  CurvesGeometry curves(4, 1);
92  curves.fill_curve_types(CURVE_TYPE_CATMULL_ROM);
93  curves.resolution_for_write().fill(12);
94  curves.offsets_for_write().last() = 4;
95  curves.cyclic_for_write().fill(false);
96 
97  MutableSpan<float3> positions = curves.positions_for_write();
98  positions[0] = {1, 1, 0};
99  positions[1] = {0, 1, 0};
100  positions[2] = {0, 0, 0};
101  positions[3] = {-1, 0, 0};
102 
103  Span<float3> evaluated_positions = curves.evaluated_positions();
104  static const Array<float3> result_1{{
105  {1, 1, 0},
106  {0.948495, 1.00318, 0},
107  {0.87963, 1.01157, 0},
108  {0.796875, 1.02344, 0},
109  {0.703704, 1.03704, 0},
110  {0.603588, 1.05064, 0},
111  {0.5, 1.0625, 0},
112  {0.396412, 1.07089, 0},
113  {0.296296, 1.07407, 0},
114  {0.203125, 1.07031, 0},
115  {0.12037, 1.05787, 0},
116  {0.0515046, 1.03501, 0},
117  {0, 1, 0},
118  {-0.0318287, 0.948495, 0},
119  {-0.0462963, 0.87963, 0},
120  {-0.046875, 0.796875, 0},
121  {-0.037037, 0.703704, 0},
122  {-0.0202546, 0.603588, 0},
123  {0, 0.5, 0},
124  {0.0202546, 0.396412, 0},
125  {0.037037, 0.296296, 0},
126  {0.046875, 0.203125, 0},
127  {0.0462963, 0.12037, 0},
128  {0.0318287, 0.0515046, 0},
129  {0, 0, 0},
130  {-0.0515046, -0.0350116, 0},
131  {-0.12037, -0.0578704, 0},
132  {-0.203125, -0.0703125, 0},
133  {-0.296296, -0.0740741, 0},
134  {-0.396412, -0.0708912, 0},
135  {-0.5, -0.0625, 0},
136  {-0.603588, -0.0506366, 0},
137  {-0.703704, -0.037037, 0},
138  {-0.796875, -0.0234375, 0},
139  {-0.87963, -0.0115741, 0},
140  {-0.948495, -0.00318287, 0},
141  {-1, 0, 0},
142  }};
143  for (const int i : evaluated_positions.index_range()) {
144  EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f);
145  }
146 
147  /* Changing the positions shouldn't cause the evaluated positions array to be reallocated. */
148  curves.tag_positions_changed();
149  curves.evaluated_positions();
150  EXPECT_EQ(curves.evaluated_positions().data(), evaluated_positions.data());
151 
152  /* Call recalculation (which shouldn't happen because low-level accessors don't tag caches). */
153  EXPECT_EQ(evaluated_positions[12].x, 0.0f);
154  EXPECT_EQ(evaluated_positions[12].y, 1.0f);
155 
156  positions[0] = {1, 0, 0};
157  positions[1] = {1, 1, 0};
158  positions[2] = {0, 1, 0};
159  positions[3] = {0, 0, 0};
160  curves.cyclic_for_write().fill(true);
161 
162  /* Tag topology changed because the new cyclic value is different. */
163  curves.tag_topology_changed();
164 
165  /* Retrieve the data again since the size should be larger than last time (one more segment). */
166  evaluated_positions = curves.evaluated_positions();
167  static const Array<float3> result_2{{
168  {1, 0, 0},
169  {1.03819, 0.0515046, 0},
170  {1.06944, 0.12037, 0},
171  {1.09375, 0.203125, 0},
172  {1.11111, 0.296296, 0},
173  {1.12153, 0.396412, 0},
174  {1.125, 0.5, 0},
175  {1.12153, 0.603588, 0},
176  {1.11111, 0.703704, 0},
177  {1.09375, 0.796875, 0},
178  {1.06944, 0.87963, 0},
179  {1.03819, 0.948495, 0},
180  {1, 1, 0},
181  {0.948495, 1.03819, 0},
182  {0.87963, 1.06944, 0},
183  {0.796875, 1.09375, 0},
184  {0.703704, 1.11111, 0},
185  {0.603588, 1.12153, 0},
186  {0.5, 1.125, 0},
187  {0.396412, 1.12153, 0},
188  {0.296296, 1.11111, 0},
189  {0.203125, 1.09375, 0},
190  {0.12037, 1.06944, 0},
191  {0.0515046, 1.03819, 0},
192  {0, 1, 0},
193  {-0.0381944, 0.948495, 0},
194  {-0.0694444, 0.87963, 0},
195  {-0.09375, 0.796875, 0},
196  {-0.111111, 0.703704, 0},
197  {-0.121528, 0.603588, 0},
198  {-0.125, 0.5, 0},
199  {-0.121528, 0.396412, 0},
200  {-0.111111, 0.296296, 0},
201  {-0.09375, 0.203125, 0},
202  {-0.0694444, 0.12037, 0},
203  {-0.0381944, 0.0515046, 0},
204  {0, 0, 0},
205  {0.0515046, -0.0381944, 0},
206  {0.12037, -0.0694444, 0},
207  {0.203125, -0.09375, 0},
208  {0.296296, -0.111111, 0},
209  {0.396412, -0.121528, 0},
210  {0.5, -0.125, 0},
211  {0.603588, -0.121528, 0},
212  {0.703704, -0.111111, 0},
213  {0.796875, -0.09375, 0},
214  {0.87963, -0.0694444, 0},
215  {0.948495, -0.0381944, 0},
216  }};
217  for (const int i : evaluated_positions.index_range()) {
218  EXPECT_V3_NEAR(evaluated_positions[i], result_2[i], 1e-5f);
219  }
220 }
221 
222 TEST(curves_geometry, CatmullRomTwoPointCyclic)
223 {
224  CurvesGeometry curves(2, 1);
225  curves.fill_curve_types(CURVE_TYPE_CATMULL_ROM);
226  curves.resolution_for_write().fill(12);
227  curves.offsets_for_write().last() = 2;
228  curves.cyclic_for_write().fill(true);
229 
230  /* The curve should still be cyclic when there are only two control points. */
231  EXPECT_EQ(curves.evaluated_points_num(), 24);
232 }
233 
234 TEST(curves_geometry, BezierPositionEvaluation)
235 {
236  CurvesGeometry curves(2, 1);
237  curves.fill_curve_types(CURVE_TYPE_BEZIER);
238  curves.resolution_for_write().fill(12);
239  curves.offsets_for_write().last() = 2;
240 
241  MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
242  MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
243  MutableSpan<float3> positions = curves.positions_for_write();
244  positions.first() = {-1, 0, 0};
245  positions.last() = {1, 0, 0};
246  handles_right.first() = {-0.5f, 0.5f, 0.0f};
247  handles_left.last() = {0, 0, 0};
248 
249  /* Dangling handles shouldn't be used in a non-cyclic curve. */
250  handles_left.first() = {100, 100, 100};
251  handles_right.last() = {100, 100, 100};
252 
253  Span<float3> evaluated_positions = curves.evaluated_positions();
254  static const Array<float3> result_1{{
255  {-1, 0, 0},
256  {-0.874711, 0.105035, 0},
257  {-0.747685, 0.173611, 0},
258  {-0.617188, 0.210937, 0},
259  {-0.481481, 0.222222, 0},
260  {-0.338831, 0.212674, 0},
261  {-0.1875, 0.1875, 0},
262  {-0.0257524, 0.15191, 0},
263  {0.148148, 0.111111, 0},
264  {0.335937, 0.0703125, 0},
265  {0.539352, 0.0347222, 0},
266  {0.760127, 0.00954859, 0},
267  {1, 0, 0},
268  }};
269  for (const int i : evaluated_positions.index_range()) {
270  EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f);
271  }
272 
273  curves.resize(4, 2);
274  curves.fill_curve_types(CURVE_TYPE_BEZIER);
275  curves.resolution_for_write().fill(9);
276  curves.offsets_for_write().last() = 4;
277  handles_left = curves.handle_positions_left_for_write();
278  handles_right = curves.handle_positions_right_for_write();
279  positions = curves.positions_for_write();
280  positions[2] = {-1, 1, 0};
281  positions[3] = {1, 1, 0};
282  handles_right[2] = {-0.5f, 1.5f, 0.0f};
283  handles_left[3] = {0, 1, 0};
284 
285  /* Dangling handles shouldn't be used in a non-cyclic curve. */
286  handles_left[2] = {-100, -100, -100};
287  handles_right[3] = {-100, -100, -100};
288 
289  evaluated_positions = curves.evaluated_positions();
290  EXPECT_EQ(evaluated_positions.size(), 20);
291  static const Array<float3> result_2{{
292  {-1, 0, 0},
293  {-0.832647, 0.131687, 0},
294  {-0.66118, 0.201646, 0},
295  {-0.481481, 0.222222, 0},
296  {-0.289438, 0.205761, 0},
297  {-0.0809327, 0.164609, 0},
298  {0.148148, 0.111111, 0},
299  {0.40192, 0.0576133, 0},
300  {0.684499, 0.016461, 0},
301  {1, 0, 0},
302  {-1, 1, 0},
303  {-0.832647, 1.13169, 0},
304  {-0.66118, 1.20165, 0},
305  {-0.481481, 1.22222, 0},
306  {-0.289438, 1.20576, 0},
307  {-0.0809327, 1.16461, 0},
308  {0.148148, 1.11111, 0},
309  {0.40192, 1.05761, 0},
310  {0.684499, 1.01646, 0},
311  {1, 1, 0},
312  }};
313  for (const int i : evaluated_positions.index_range()) {
314  EXPECT_V3_NEAR(evaluated_positions[i], result_2[i], 1e-5f);
315  }
316 }
317 
318 TEST(curves_geometry, NURBSEvaluation)
319 {
320  CurvesGeometry curves(4, 1);
321  curves.fill_curve_types(CURVE_TYPE_NURBS);
322  curves.resolution_for_write().fill(10);
323  curves.offsets_for_write().last() = 4;
324 
325  MutableSpan<float3> positions = curves.positions_for_write();
326  positions[0] = {1, 1, 0};
327  positions[1] = {0, 1, 0};
328  positions[2] = {0, 0, 0};
329  positions[3] = {-1, 0, 0};
330 
331  Span<float3> evaluated_positions = curves.evaluated_positions();
332  static const Array<float3> result_1{{
333  {0.166667, 0.833333, 0}, {0.150006, 0.815511, 0}, {0.134453, 0.796582, 0},
334  {0.119924, 0.776627, 0}, {0.106339, 0.75573, 0}, {0.0936146, 0.733972, 0},
335  {0.0816693, 0.711434, 0}, {0.0704211, 0.6882, 0}, {0.0597879, 0.66435, 0},
336  {0.0496877, 0.639968, 0}, {0.0400385, 0.615134, 0}, {0.0307584, 0.589931, 0},
337  {0.0217653, 0.564442, 0}, {0.0129772, 0.538747, 0}, {0.00431208, 0.512929, 0},
338  {-0.00431208, 0.487071, 0}, {-0.0129772, 0.461253, 0}, {-0.0217653, 0.435558, 0},
339  {-0.0307584, 0.410069, 0}, {-0.0400385, 0.384866, 0}, {-0.0496877, 0.360032, 0},
340  {-0.0597878, 0.33565, 0}, {-0.0704211, 0.3118, 0}, {-0.0816693, 0.288566, 0},
341  {-0.0936146, 0.266028, 0}, {-0.106339, 0.24427, 0}, {-0.119924, 0.223373, 0},
342  {-0.134453, 0.203418, 0}, {-0.150006, 0.184489, 0}, {-0.166667, 0.166667, 0},
343  }};
344  for (const int i : evaluated_positions.index_range()) {
345  EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f);
346  }
347 
348  /* Test a cyclic curve. */
349  curves.cyclic_for_write().fill(true);
350  curves.tag_topology_changed();
351  evaluated_positions = curves.evaluated_positions();
352  static const Array<float3> result_2{{
353  {0.166667, 0.833333, 0}, {0.121333, 0.778667, 0},
354  {0.084, 0.716, 0}, {0.0526667, 0.647333, 0},
355  {0.0253333, 0.574667, 0}, {0, 0.5, 0},
356  {-0.0253333, 0.425333, 0}, {-0.0526667, 0.352667, 0},
357  {-0.084, 0.284, 0}, {-0.121333, 0.221333, 0},
358  {-0.166667, 0.166667, 0}, {-0.221, 0.121667, 0},
359  {-0.281333, 0.0866667, 0}, {-0.343667, 0.0616666, 0},
360  {-0.404, 0.0466667, 0}, {-0.458333, 0.0416667, 0},
361  {-0.502667, 0.0466667, 0}, {-0.533, 0.0616666, 0},
362  {-0.545333, 0.0866667, 0}, {-0.535667, 0.121667, 0},
363  {-0.5, 0.166667, 0}, {-0.436, 0.221334, 0},
364  {-0.348, 0.284, 0}, {-0.242, 0.352667, 0},
365  {-0.124, 0.425333, 0}, {0, 0.5, 0},
366  {0.124, 0.574667, 0}, {0.242, 0.647333, 0},
367  {0.348, 0.716, 0}, {0.436, 0.778667, 0},
368  {0.5, 0.833333, 0}, {0.535667, 0.878334, 0},
369  {0.545333, 0.913333, 0}, {0.533, 0.938333, 0},
370  {0.502667, 0.953333, 0}, {0.458333, 0.958333, 0},
371  {0.404, 0.953333, 0}, {0.343667, 0.938333, 0},
372  {0.281333, 0.913333, 0}, {0.221, 0.878333, 0},
373  }};
374  for (const int i : evaluated_positions.index_range()) {
375  EXPECT_V3_NEAR(evaluated_positions[i], result_2[i], 1e-5f);
376  }
377 
378  /* Test a circular cyclic curve with weights. */
379  positions[0] = {1, 0, 0};
380  positions[1] = {1, 1, 0};
381  positions[2] = {0, 1, 0};
382  positions[3] = {0, 0, 0};
383  curves.nurbs_weights_for_write().fill(1.0f);
384  curves.nurbs_weights_for_write()[0] = 4.0f;
385  curves.tag_positions_changed();
386  static const Array<float3> result_3{{
387  {0.888889, 0.555556, 0}, {0.837792, 0.643703, 0}, {0.773885, 0.727176, 0},
388  {0.698961, 0.800967, 0}, {0.616125, 0.860409, 0}, {0.529412, 0.901961, 0},
389  {0.443152, 0.923773, 0}, {0.361289, 0.925835, 0}, {0.286853, 0.909695, 0},
390  {0.221722, 0.877894, 0}, {0.166667, 0.833333, 0}, {0.122106, 0.778278, 0},
391  {0.0903055, 0.713148, 0}, {0.0741654, 0.638711, 0}, {0.0762274, 0.556847, 0},
392  {0.0980392, 0.470588, 0}, {0.139591, 0.383875, 0}, {0.199032, 0.301039, 0},
393  {0.272824, 0.226114, 0}, {0.356297, 0.162208, 0}, {0.444444, 0.111111, 0},
394  {0.531911, 0.0731388, 0}, {0.612554, 0.0468976, 0}, {0.683378, 0.0301622, 0},
395  {0.74391, 0.0207962, 0}, {0.794872, 0.017094, 0}, {0.837411, 0.017839, 0},
396  {0.872706, 0.0222583, 0}, {0.901798, 0.0299677, 0}, {0.925515, 0.0409445, 0},
397  {0.944444, 0.0555556, 0}, {0.959056, 0.0744855, 0}, {0.970032, 0.0982019, 0},
398  {0.977742, 0.127294, 0}, {0.982161, 0.162589, 0}, {0.982906, 0.205128, 0},
399  {0.979204, 0.256091, 0}, {0.969838, 0.316622, 0}, {0.953102, 0.387446, 0},
400  {0.926861, 0.468089, 0},
401  }};
402  evaluated_positions = curves.evaluated_positions();
403  for (const int i : evaluated_positions.index_range()) {
404  EXPECT_V3_NEAR(evaluated_positions[i], result_3[i], 1e-5f);
405  }
406 }
407 
408 TEST(curves_geometry, BezierGenericEvaluation)
409 {
410  CurvesGeometry curves(3, 1);
411  curves.fill_curve_types(CURVE_TYPE_BEZIER);
412  curves.resolution_for_write().fill(8);
413  curves.offsets_for_write().last() = 3;
414 
415  MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
416  MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
417  MutableSpan<float3> positions = curves.positions_for_write();
418  positions.first() = {-1, 0, 0};
419  handles_right.first() = {-1, 1, 0};
420  handles_left[1] = {0, 0, 0};
421  positions[1] = {1, 0, 0};
422  handles_right[1] = {2, 0, 0};
423  handles_left.last() = {1, 1, 0};
424  positions.last() = {2, 1, 0};
425 
426  /* Dangling handles shouldn't be used in a non-cyclic curve. */
427  handles_left.first() = {100, 100, 100};
428  handles_right.last() = {100, 100, 100};
429 
430  Span<float3> evaluated_positions = curves.evaluated_positions();
431  static const Array<float3> result_1{{
432  {-1.0f, 0.0f, 0.0f},
433  {-0.955078f, 0.287109f, 0.0f},
434  {-0.828125f, 0.421875f, 0.0f},
435  {-0.630859f, 0.439453f, 0.0f},
436  {-0.375f, 0.375f, 0.0f},
437  {-0.0722656f, 0.263672f, 0.0f},
438  {0.265625f, 0.140625f, 0.0f},
439  {0.626953f, 0.0410156f, 0.0f},
440  {1.0f, 0.0f, 0.0f},
441  {1.28906f, 0.0429688f, 0.0f},
442  {1.4375f, 0.15625f, 0.0f},
443  {1.49219f, 0.316406f, 0.0f},
444  {1.5f, 0.5f, 0.0f},
445  {1.50781f, 0.683594f, 0.0f},
446  {1.5625f, 0.84375f, 0.0f},
447  {1.71094f, 0.957031f, 0.0f},
448  {2.0f, 1.0f, 0.0f},
449  }};
450  for (const int i : evaluated_positions.index_range()) {
451  EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f);
452  }
453 
454  Array<float> radii{{0.0f, 1.0f, 2.0f}};
455  Array<float> evaluated_radii(17);
456  curves.interpolate_to_evaluated(0, radii.as_span(), evaluated_radii.as_mutable_span());
457  static const Array<float> result_2{{
458  0.0f,
459  0.125f,
460  0.25f,
461  0.375f,
462  0.5f,
463  0.625f,
464  0.75f,
465  0.875f,
466  1.0f,
467  1.125f,
468  1.25f,
469  1.375f,
470  1.5f,
471  1.625f,
472  1.75f,
473  1.875f,
474  2.0f,
475  }};
476  for (const int i : evaluated_radii.index_range()) {
477  EXPECT_NEAR(evaluated_radii[i], result_2[i], 1e-6f);
478  }
479 }
480 
481 } // namespace blender::bke::tests
typedef float(TangentPoint)[2]
Low-level operations for curves.
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_NURBS
@ CURVE_TYPE_POLY
@ CURVE_TYPE_CATMULL_ROM
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum const void *lists _GL_VOID_RET _GL_VOID const GLdouble *equation _GL_VOID_RET _GL_VOID GLdouble GLdouble blue _GL_VOID_RET _GL_VOID GLfloat GLfloat blue _GL_VOID_RET _GL_VOID GLint GLint blue _GL_VOID_RET _GL_VOID GLshort GLshort blue _GL_VOID_RET _GL_VOID GLubyte GLubyte blue _GL_VOID_RET _GL_VOID GLuint GLuint blue _GL_VOID_RET _GL_VOID GLushort GLushort blue _GL_VOID_RET _GL_VOID GLbyte GLbyte GLbyte alpha _GL_VOID_RET _GL_VOID GLdouble GLdouble GLdouble alpha _GL_VOID_RET _GL_VOID GLfloat GLfloat GLfloat alpha _GL_VOID_RET _GL_VOID GLint GLint GLint alpha _GL_VOID_RET _GL_VOID GLshort GLshort GLshort alpha _GL_VOID_RET _GL_VOID GLubyte GLubyte GLubyte alpha _GL_VOID_RET _GL_VOID GLuint GLuint GLuint alpha _GL_VOID_RET _GL_VOID GLushort GLushort GLushort alpha _GL_VOID_RET _GL_VOID GLenum mode _GL_VOID_RET _GL_VOID GLint y
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Bright Control the brightness and contrast of the input color Vector Map an input vectors to curves
IndexRange index_range() const
Definition: BLI_array.hh:348
MutableSpan< T > as_mutable_span()
Definition: BLI_array.hh:236
constexpr T & last(const int64_t n=0) const
Definition: BLI_span.hh:680
constexpr T & first() const
Definition: BLI_span.hh:670
constexpr const T * data() const
Definition: BLI_span.hh:203
constexpr int64_t size() const
Definition: BLI_span.hh:240
constexpr IndexRange index_range() const
Definition: BLI_span.hh:401
Span< float3 > positions() const
bool bounds_min_max(float3 &min, float3 &max) const
VArray< bool > cyclic() const
TEST(action_groups, ReconstructGroupsWithReordering)
Definition: action_test.cc:17
static CurvesGeometry create_basic_curves(const int points_size, const int curves_size)
MutableSpan< float3 > positions
MutableSpan< float > radii
#define min(a, b)
Definition: sort.c:35
float max