Blender V5.0
curves_edit_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "ED_curves.hh"
10
11#include "testing/testing.h"
12
14
16 const int order,
17 const Set<int> &is_cyclic)
18{
19 Array<int> offsets(all_positions.size() + 1, 0);
20 for (const int curve : all_positions.index_range()) {
21 const Vector<float3> &curve_positions = all_positions[curve];
22 offsets[curve + 1] = offsets[curve] + curve_positions.size();
23 }
24
25 bke::CurvesGeometry curves(offsets.last(), all_positions.size());
26
27 curves.offsets_for_write().copy_from(offsets);
28 const OffsetIndices points_by_curve = curves.points_by_curve();
29 MutableSpan<float3> positions = curves.positions_for_write();
30 MutableSpan<bool> cyclic = curves.cyclic_for_write();
31 MutableSpan<int8_t> orders = curves.nurbs_orders_for_write();
32
33 for (const int curve : all_positions.index_range()) {
34 positions.slice(points_by_curve[curve]).copy_from(all_positions[curve]);
35 cyclic[curve] = is_cyclic.contains(curve);
36 orders[curve] = order;
37 }
38
39 curves.tag_topology_changed();
40 return curves;
41}
42
44 const int order,
45 const Set<int> &is_cyclic)
46{
47 return create_curves(Span<Vector<float3>>(&positions, 1), order, is_cyclic);
48}
49
50static void validate_positions(const Span<Vector<float3>> expected_positions,
51 const OffsetIndices<int> points_by_curve,
52 const Span<float3> positions)
53{
54 for (const int curve : expected_positions.index_range()) {
55 const Span<float3> expected_curve_positions = expected_positions[curve];
56 const IndexRange points = points_by_curve[curve];
57 for (const int point : expected_curve_positions.index_range()) {
58 EXPECT_EQ(positions[points[point]], expected_curve_positions[point]);
59 }
60 }
61}
62
63TEST(curves_editors, DuplicatePointsTwoSingle)
64{
65 /* Two points from single curve. */
66 const Vector<float3> expected_positions = {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}};
67
68 bke::CurvesGeometry curves = create_curves(expected_positions, 4, {});
69 IndexMaskMemory memory;
71
73
74 EXPECT_TRUE(curves.curves_num() == 2);
75
76 const Span<float3> positions = curves.positions();
77
78 for (const int point : expected_positions.index_range()) {
79 EXPECT_TRUE(positions[point] == expected_positions[point]);
80 }
81
82 EXPECT_TRUE(positions[4] == expected_positions[1]);
83 EXPECT_TRUE(positions[5] == expected_positions[2]);
84}
85
86TEST(curves_editors, DuplicatePointsFourThree)
87{
88 /* Four points from three curves. One curve has one point. */
89 const Vector<Vector<float3>> expected_positions = {
90 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
91 {{0, 0, 0}},
92 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
93
94 bke::CurvesGeometry curves = create_curves(expected_positions, 4, {});
95 IndexMaskMemory memory;
96 const IndexMask mask = IndexMask::from_indices(Array<int>{0, 1, 4, 9}.as_span(), memory);
97
99
100 EXPECT_TRUE(curves.curves_num() == expected_positions.size() + 3);
101
102 const Span<float3> positions = curves.positions();
103 const OffsetIndices points_by_curve = curves.points_by_curve();
104
105 for (const int curve : expected_positions.index_range()) {
106 const Span<float3> expected_curve_positions = expected_positions[curve];
107 const IndexRange points = points_by_curve[curve];
108 for (const int point : expected_curve_positions.index_range()) {
109 EXPECT_TRUE(positions[points[point]] == expected_curve_positions[point]);
110 }
111 }
112
113 EXPECT_TRUE(positions[10] == expected_positions[0][0]);
114 EXPECT_TRUE(positions[11] == expected_positions[0][1]);
115 EXPECT_TRUE(positions[12] == expected_positions[1][0]);
116 EXPECT_TRUE(positions[13] == expected_positions[2][4]);
117}
118
119TEST(curves_editors, DuplicatePointsTwoCyclic)
120{
121 /* Two points from cyclic curve. Points are on cycle. */
122 const Vector<Vector<float3>> expected_positions = {
123 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
124 {{0, 0, 0}},
125 {{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
126 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
127
128 bke::CurvesGeometry curves = create_curves(expected_positions, 4, {2});
129 IndexMaskMemory memory;
131
133
134 EXPECT_TRUE(curves.curves_num() == expected_positions.size() + 1);
135
136 const Span<float3> positions = curves.positions();
137 const OffsetIndices points_by_curve = curves.points_by_curve();
138
139 for (const int curve : expected_positions.index_range()) {
140 const Span<float3> expected_curve_positions = expected_positions[curve];
141 const IndexRange points = points_by_curve[curve];
142 for (const int point : expected_curve_positions.index_range()) {
143 EXPECT_TRUE(positions[points[point]] == expected_curve_positions[point]);
144 }
145 }
146
147 EXPECT_TRUE(positions[14] == expected_positions[2][3]);
148 EXPECT_TRUE(positions[15] == expected_positions[2][0]);
149}
150
151TEST(curves_editors, SplitPointsTwoSingle)
152{
153 /* Split two points from single curve. */
154 const Vector<float3> positions = {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}};
155
156 bke::CurvesGeometry curves = create_curves(positions, 4, {});
157 IndexMaskMemory memory;
158 const IndexMask mask = IndexMask::from_indices<int>({1, 2}, memory);
159
161
162 const Vector<Vector<float3>> expected_positions = {
163 {{-1, 1, 0}, {1, 1, 0}}, {{-1.5, 0, 0}, {-1, 1, 0}}, {{1, 1, 0}, {1.5, 0, 0}}};
164
165 GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
166 validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
167}
168
169TEST(curves_editors, SplitPointsFourThree)
170{
171 /* Four points from three curves. One curve has one point. */
172 const Vector<Vector<float3>> positions = {
173 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
174 {{0, 0, 0}},
175 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
176
177 bke::CurvesGeometry curves = create_curves(positions, 4, {});
178 IndexMaskMemory memory;
179 const IndexMask mask = IndexMask::from_indices(Array<int>{0, 1, 4, 9}.as_span(), memory);
180
182
183 const Vector<Vector<float3>> expected_positions = {
184 {{-1.5, 0, 0}, {-1, 1, 0}},
185 {{-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
186 {{0, 0, 0}},
187 {{1, -1, 0}},
188 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
189
190 GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
191 validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
192}
193
194TEST(curves_editors, SplitPointsTwoCyclic)
195{
196 /* Two points from cyclic curve. Points are on cycle. */
197 const Vector<Vector<float3>> positions = {
198 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
199 {{0, 0, 0}},
200 {{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
201 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
202
203 bke::CurvesGeometry curves = create_curves(positions, 4, {2});
204 IndexMaskMemory memory;
206
208
209 const Vector<Vector<float3>> expected_positions = {
210 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
211 {{0, 0, 0}},
212 {{-1, 1, 0}, {1, 1, 0}},
213 {{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
214 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
215
216 GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
217 validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
218 Array<bool> expected_cyclic = {false, false, false, false, false};
219 VArray<bool> cyclic = new_curves.cyclic();
220 for (const int i : expected_cyclic.index_range()) {
221 EXPECT_EQ(expected_cyclic[i], cyclic[i]);
222 }
223}
224
225TEST(curves_editors, SplitPointsTwoTouchCyclic)
226{
227 /* Two points from cyclic curve. Points are touching cycle. */
228 const Vector<Vector<float3>> positions = {
229 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
230 {{0, 0, 0}},
231 {{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
232 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
233
234 bke::CurvesGeometry curves = create_curves(positions, 4, {2});
235 IndexMaskMemory memory;
237
239
240 const Vector<Vector<float3>> expected_positions = {
241 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
242 {{0, 0, 0}},
243 {{1, 1, 0}, {1, -1, 0}},
244 {{1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}, {1, 1, 0}},
245 {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
246
247 GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
248 validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
249}
250
251TEST(curves_editors, SplitEverySecondCyclic)
252{
253 /* Split every second point in cyclic curve. Expected result all selected points
254 * as separate curves and original curve. */
255 const Vector<Vector<float3>> positions = {{{0, -1, 0},
256 {-1, -1, 0},
257 {-1, 0, 0},
258 {-1, 1, 0},
259 {0, 1, 0},
260 {1, 1, 0},
261 {1, 0, 0},
262 {1, -1, 0}}};
263
264 bke::CurvesGeometry curves = create_curves(positions, 4, {0});
265 IndexMaskMemory memory;
266 const IndexMask mask = IndexMask::from_indices(Array<int>{0, 2, 4, 6}.as_span(), memory);
267
269
270 const Vector<Vector<float3>> expected_positions = {{{0, -1, 0}},
271 {{-1, 0, 0}},
272 {{0, 1, 0}},
273 {{1, 0, 0}},
274 {{0, -1, 0},
275 {-1, -1, 0},
276 {-1, 0, 0},
277 {-1, 1, 0},
278 {0, 1, 0},
279 {1, 1, 0},
280 {1, 0, 0},
281 {1, -1, 0}}};
282
283 GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
284 validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
285}
286
287TEST(curves_editors, SplitAllSelectedButFirstCyclic)
288{
289 /* Split all except first points in cyclic curve. Expected result two curves. One from selected
290 * points another from first, second and last. Both not cyclic. */
291 const Vector<Vector<float3>> positions = {{{0, -1, 0},
292 {-1, -1, 0},
293 {-1, 0, 0},
294 {-1, 1, 0},
295 {0, 1, 0},
296 {1, 1, 0},
297 {1, 0, 0},
298 {1, -1, 0}}};
299
300 bke::CurvesGeometry curves = create_curves(positions, 4, {0});
301 IndexMaskMemory memory;
302 const IndexMask mask = IndexMask::from_indices(Array<int>{1, 2, 3, 4, 5, 6, 7}.as_span(),
303 memory);
304
306
307 const Vector<Vector<float3>> expected_positions = {
308 {{-1, -1, 0}, {-1, 0, 0}, {-1, 1, 0}, {0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {1, -1, 0}},
309 {{1, -1, 0}, {0, -1, 0}, {-1, -1, 0}},
310 };
311
312 GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
313 validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
314 EXPECT_EQ(new_curves.curves_num(), expected_positions.size());
315 EXPECT_FALSE(new_curves.cyclic()[0]);
316 EXPECT_FALSE(new_curves.cyclic()[1]);
317}
318
319TEST(curves_editors, SplitTwoOnSeamAndExtraCyclic)
320{
321 /* Split first, last and pair in the middle. Expected result four non cyclic curves. */
322 const Vector<Vector<float3>> positions = {{{0, -1, 0},
323 {-1, -1, 0},
324 {-1, 0, 0},
325 {-1, 1, 0},
326 {0, 1, 0},
327 {1, 1, 0},
328 {1, 0, 0},
329 {1, -1, 0}}};
330
331 bke::CurvesGeometry curves = create_curves(positions, 4, {0});
332 IndexMaskMemory memory;
333 const IndexMask mask = IndexMask::from_indices(Array<int>{0, 3, 4, 7}.as_span(), memory);
334
336
337 const Vector<Vector<float3>> expected_positions = {
338 {{-1, 1, 0}, {0, 1, 0}},
339 {{1, -1, 0}, {0, -1, 0}},
340 {{0, -1, 0}, {-1, -1, 0}, {-1, 0, 0}, {-1, 1, 0}},
341 {{0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {1, -1, 0}}};
342
343 GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
344 validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
345 EXPECT_FALSE(new_curves.cyclic()[0]);
346 EXPECT_FALSE(new_curves.cyclic()[1]);
347 EXPECT_FALSE(new_curves.cyclic()[2]);
348 EXPECT_FALSE(new_curves.cyclic()[3]);
349}
350
351} // namespace blender::ed::curves::tests
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
Span< T > as_span() const
Definition BLI_array.hh:243
const T & last(const int64_t n=0) const
Definition BLI_array.hh:296
IndexRange index_range() const
Definition BLI_array.hh:360
static IndexMask from_indices(Span< T > indices, IndexMaskMemory &memory)
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
int64_t size() const
IndexRange index_range() const
OffsetIndices< int > points_by_curve() const
Span< float3 > positions() const
VArray< bool > cyclic() const
static bool is_cyclic(const Nurb *nu)
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static void validate_positions(const Span< Vector< float3 > > expected_positions, const OffsetIndices< int > points_by_curve, const Span< float3 > positions)
static bke::CurvesGeometry create_curves(const Span< Vector< float3 > > all_positions, const int order, const Set< int > &is_cyclic)
TEST(curves_editors, DuplicatePointsTwoSingle)
void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
bke::CurvesGeometry split_points(const bke::CurvesGeometry &curves, const IndexMask &points_to_split)
i
Definition text_draw.cc:230