Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
bezier-test.cpp
Go to the documentation of this file.
1/*
5 * Authors:
6 * Nathan Hurst <njh@njhurst.com>
7 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
8 * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
9 *
10 * Copyright 2010 Authors
11 *
12 * This library is free software; you can redistribute it and/or
13 * modify it either under the terms of the GNU Lesser General Public
14 * License version 2.1 as published by the Free Software Foundation
15 * (the "LGPL") or, at your option, under the terms of the Mozilla
16 * Public License Version 1.1 (the "MPL"). If you do not alter this
17 * notice, a recipient may use your version of this file under either
18 * the MPL or the LGPL.
19 *
20 * You should have received a copy of the LGPL along with this library
21 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 * You should have received a copy of the MPL along with this library
24 * in the file COPYING-MPL-1.1
25 *
26 * The contents of this file are subject to the Mozilla Public License
27 * Version 1.1 (the "License"); you may not use this file except in
28 * compliance with the License. You may obtain a copy of the License at
29 * http://www.mozilla.org/MPL/
30 *
31 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
32 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
33 * the specific language governing rights and limitations.
34 */
35
36#include "testing.h"
37#include <iostream>
38
39#include <2geom/bezier.h>
40#include <2geom/polynomial.h>
42#include <2geom/bezier-curve.h>
43#include <vector>
44#include <glib.h>
45
46using std::vector, std::min, std::max;
47using namespace Geom;
48
49Poly lin_poly(double a, double b) { // ax + b
50 Poly p;
51 p.push_back(b);
52 p.push_back(a);
53 return p;
54}
55
57 int maxSize = max(A.size(), B.size());
58 double t = 0., dt = 1./maxSize;
59
60 for(int i = 0; i <= maxSize; i++) {
61 EXPECT_FLOAT_EQ(A.valueAt(t), B.valueAt(t));// return false;
62 t += dt;
63 }
64 return true;
65}
66
67class BezierTest : public ::testing::Test {
68protected:
69
70 BezierTest()
71 : zero(fragments[0])
72 , unit(fragments[1])
73 , hump(fragments[2])
74 , wiggle(fragments[3])
75 {
76 zero = Bezier(0.0,0.0);
77 unit = Bezier(0.0,1.0);
78 hump = Bezier(0,1,0);
79 wiggle = Bezier(0,1,-2,3);
80 }
81
82 Bezier fragments[4];
83 Bezier &zero, &unit, &hump, &wiggle;
84};
85
86TEST_F(BezierTest, Basics) {
87
88 //std::cout << unit <<std::endl;
89 //std::cout << hump <<std::endl;
90
91 EXPECT_TRUE(Bezier(0,0,0,0).isZero());
92 EXPECT_TRUE(Bezier(0,1,2,3).isFinite());
93
94 EXPECT_EQ(3u, Bezier(0,2,4,5).order());
95
97 //cout << Bezier(wiggle) << " == " << wiggle << endl;
98
99 //cout << "explicit Bezier(unsigned ord);\n";
100 //cout << Bezier(10) << endl;
101
102 //cout << "Bezier(Coord c0, Coord c1);\n";
103 //cout << Bezier(0.0,1.0) << endl;
104
105 //cout << "Bezier(Coord c0, Coord c1, Coord c2);\n";
106 //cout << Bezier(0,1, 2) << endl;
107
108 //cout << "Bezier(Coord c0, Coord c1, Coord c2, Coord c3);\n";
109 //cout << Bezier(0,1,2,3) << endl;
110
111 //cout << "unsigned degree();\n";
112 EXPECT_EQ(2u, hump.degree());
113
114 //cout << "unsigned size();\n";
115 EXPECT_EQ(3u, hump.size());
116}
117
118TEST_F(BezierTest, ValueAt) {
119 EXPECT_EQ(0.0, wiggle.at0());
120 EXPECT_EQ(3.0, wiggle.at1());
121
122 EXPECT_EQ(0.0, wiggle.valueAt(0.5));
123
124 EXPECT_EQ(0.0, wiggle(0.5));
125
126 //cout << "SBasis toSBasis();\n";
127 //cout << unit.toSBasis() << endl;
128 //cout << hump.toSBasis() << endl;
129 //cout << wiggle.toSBasis() << endl;
130}
131
132TEST_F(BezierTest, Casteljau) {
133 unsigned N = wiggle.order() + 1;
134 std::vector<Coord> left(N), right(N);
135 std::vector<Coord> left2(N), right2(N);
136 double eps = 1e-15;
137
138 for (unsigned i = 0; i < 10000; ++i) {
139 double t = g_random_double_range(0, 1);
140 double vok = bernstein_value_at(t, &wiggle[0], wiggle.order());
141 double v = casteljau_subdivision<double>(t, &wiggle[0], &left[0], &right[0], wiggle.order());
142 EXPECT_near(v, vok, eps);
143 EXPECT_EQ(left[0], wiggle.at0());
144 EXPECT_EQ(left[wiggle.order()], right[0]);
145 EXPECT_EQ(right[wiggle.order()], wiggle.at1());
146
147 double vl = casteljau_subdivision<double>(t, &wiggle[0], &left2[0], NULL, wiggle.order());
148 double vr = casteljau_subdivision<double>(t, &wiggle[0], NULL, &right2[0], wiggle.order());
149 EXPECT_EQ(vl, vok);
150 EXPECT_near(vr, vok, eps);
151 EXPECT_vector_near(left2, left, eps);
152 EXPECT_vector_equal(right2, right);
153
154 double vnone = casteljau_subdivision<double>(t, &wiggle[0], NULL, NULL, wiggle.order());
155 EXPECT_near(vnone, vok, 1e-12);
156 }
157}
158
159TEST_F(BezierTest, Portion) {
160 constexpr Coord eps{1e-12};
161
162 for (unsigned i = 0; i < 10000; ++i) {
163 double from = g_random_double_range(0, 1);
164 double to = g_random_double_range(0, 1);
165 for (auto & input : fragments) {
166 Bezier result = portion(input, from, to);
167
168 // the endpoints must correspond exactly
169 EXPECT_near(result.at0(), input.valueAt(from), eps);
170 EXPECT_near(result.at1(), input.valueAt(to), eps);
171 }
172 }
173}
174
175TEST_F(BezierTest, Subdivide) {
176 std::vector<std::pair<Bezier, double> > errors;
177 for (unsigned i = 0; i < 10000; ++i) {
178 double t = g_random_double_range(0, 1e-6);
179 for (auto & input : fragments) {
180 std::pair<Bezier, Bezier> result = input.subdivide(t);
181
182 // the endpoints must correspond exactly
183 // moreover, the subdivision point must be exactly equal to valueAt(t)
184 EXPECT_DOUBLE_EQ(result.first.at0(), input.at0());
185 EXPECT_DOUBLE_EQ(result.first.at1(), result.second.at0());
186 EXPECT_DOUBLE_EQ(result.second.at0(), input.valueAt(t));
187 EXPECT_DOUBLE_EQ(result.second.at1(), input.at1());
188
189 // ditto for valueAt
190 EXPECT_DOUBLE_EQ(result.first.valueAt(0), input.valueAt(0));
191 EXPECT_DOUBLE_EQ(result.first.valueAt(1), result.second.valueAt(0));
192 EXPECT_DOUBLE_EQ(result.second.valueAt(0), input.valueAt(t));
193 EXPECT_DOUBLE_EQ(result.second.valueAt(1), input.valueAt(1));
194
195 if (result.first.at1() != result.second.at0()) {
196 errors.emplace_back(input, t);
197 }
198 }
199 }
200 if (!errors.empty()) {
201 std::cout << "Found " << errors.size() << " subdivision errors" << std::endl;
202 for (unsigned i = 0; i < errors.size(); ++i) {
203 std::cout << "Error #" << i << ":\n"
204 << errors[i].first << "\n"
205 << "t: " << format_coord_nice(errors[i].second) << std::endl;
206 }
207 }
208}
209
210TEST_F(BezierTest, Mutation) {
211//Coord &operator[](unsigned ix);
212//Coord const &operator[](unsigned ix);
213//void setCoeff(unsigned ix double val);
214 //cout << "bigun\n";
215 Bezier bigun(Bezier::Order(30));
216 bigun.setCoeff(5,10.0);
217 for(unsigned i = 0; i < bigun.size(); i++) {
218 EXPECT_EQ((i == 5) ? 10 : 0, bigun[i]);
219 }
220
221 bigun[5] = -3;
222 for(unsigned i = 0; i < bigun.size(); i++) {
223 EXPECT_EQ((i == 5) ? -3 : 0, bigun[i]);
224 }
225}
226
227TEST_F(BezierTest, MultiDerivative) {
228 vector<double> vnd = wiggle.valueAndDerivatives(0.5, 5);
229 expect_array((const double[]){0,0,12,72,0,0}, vnd);
230}
231
232TEST_F(BezierTest, DegreeElevation) {
233 EXPECT_TRUE(are_equal(wiggle, wiggle));
234 Bezier Q = wiggle;
235 Bezier P = Q.elevate_degree();
236 EXPECT_EQ(P.size(), Q.size()+1);
237 //EXPECT_EQ(0, P.forward_difference(1)[0]);
238 EXPECT_TRUE(are_equal(Q, P));
239 Q = wiggle;
240 P = Q.elevate_to_degree(10);
241 EXPECT_EQ(10u, P.order());
242 EXPECT_TRUE(are_equal(Q, P));
243 //EXPECT_EQ(0, P.forward_difference(10)[0]);
244 /*Q = wiggle.elevate_degree();
245 P = Q.reduce_degree();
246 EXPECT_EQ(P.size()+1, Q.size());
247 EXPECT_TRUE(are_equal(Q, P));*/
248}
249//std::pair<Bezier, Bezier > subdivide(Coord t);
250
251// Constructs a linear Bezier with root at t
253 return Bezier(0-t, 1-t);
254}
255
256// Constructs a Bezier with roots at the locations in x
257Bezier array_roots(vector<double> x) {
258 Bezier b(1);
259 for(double i : x) {
260 b = multiply(b, linear_root(i));
261 }
262 return b;
263}
264
265TEST_F(BezierTest, Deflate) {
266 Bezier b = array_roots(vector_from_array((const double[]){0,0.25,0.5}));
267 EXPECT_FLOAT_EQ(0, b.at0());
268 b = b.deflate();
269 EXPECT_FLOAT_EQ(0, b.valueAt(0.25));
270 b = b.subdivide(0.25).second;
271 EXPECT_FLOAT_EQ(0, b.at0());
272 b = b.deflate();
273 const double rootposition = (0.5-0.25) / (1-0.25);
274 constexpr Coord eps{1e-12};
275 EXPECT_near(0.0, b.valueAt(rootposition), eps);
276 b = b.subdivide(rootposition).second;
277 EXPECT_near(0.0, b.at0(), eps);
278}
279
280TEST_F(BezierTest, Roots) {
281 expect_array((const double[]){0, 0.5, 0.5}, wiggle.roots());
282
283 /*Bezier bigun(Bezier::Order(30));
284 for(unsigned i = 0; i < bigun.size(); i++) {
285 bigun.setCoeff(i,rand()-0.5);
286 }
287 cout << bigun.roots() << endl;*/
288
289 // The results of our rootfinding are at the moment fairly inaccurate.
290 double eps = 5e-4;
291
292 vector<vector<double> > tests;
293 tests.push_back(vector_from_array((const double[]){0}));
294 tests.push_back(vector_from_array((const double[]){1}));
295 tests.push_back(vector_from_array((const double[]){0, 0}));
296 tests.push_back(vector_from_array((const double[]){0.5}));
297 tests.push_back(vector_from_array((const double[]){0.5, 0.5}));
298 tests.push_back(vector_from_array((const double[]){0.1, 0.1}));
299 tests.push_back(vector_from_array((const double[]){0.1, 0.1, 0.1}));
300 tests.push_back(vector_from_array((const double[]){0.25,0.75}));
301 tests.push_back(vector_from_array((const double[]){0.5,0.5}));
302 tests.push_back(vector_from_array((const double[]){0, 0.2, 0.6, 0.6, 1}));
303 tests.push_back(vector_from_array((const double[]){.1,.2,.3,.4,.5,.6}));
304 tests.push_back(vector_from_array((const double[]){0.25,0.25,0.25,0.75,0.75,0.75}));
305
306 for(auto & test : tests) {
308 //std::cout << tests[test_i] << ": " << b << std::endl;
309 //std::cout << b.roots() << std::endl;
310 EXPECT_vector_near(test, b.roots(), eps);
311 }
312}
313
314TEST_F(BezierTest, BoundsExact) {
315 OptInterval unit_bounds = bounds_exact(unit);
316 EXPECT_EQ(unit_bounds->min(), 0);
317 EXPECT_EQ(unit_bounds->max(), 1);
318
319 OptInterval hump_bounds = bounds_exact(hump);
320 EXPECT_EQ(hump_bounds->min(), 0);
321 EXPECT_FLOAT_EQ(hump_bounds->max(), hump.valueAt(0.5));
322
323 OptInterval wiggle_bounds = bounds_exact(wiggle);
324 EXPECT_EQ(wiggle_bounds->min(), 0);
325 EXPECT_EQ(wiggle_bounds->max(), 3);
326}
327
328TEST_F(BezierTest, Operators) {
329 // Test equality operators
330 EXPECT_EQ(zero, zero);
331 EXPECT_EQ(hump, hump);
332 EXPECT_EQ(wiggle, wiggle);
333 EXPECT_EQ(unit, unit);
334
335 EXPECT_NE(zero, hump);
336 EXPECT_NE(hump, zero);
337 EXPECT_NE(wiggle, hump);
338 EXPECT_NE(zero, wiggle);
339 EXPECT_NE(wiggle, unit);
340
341 // Recall that hump == Bezier(0,1,0);
342 EXPECT_EQ(hump + 3, Bezier(3, 4, 3));
343 EXPECT_EQ(hump - 3, Bezier(-3, -2, -3));
344 EXPECT_EQ(hump * 3, Bezier(0, 3, 0));
345 EXPECT_EQ(hump / 3, Bezier(0, 1.0/3.0, 0));
346 EXPECT_EQ(-hump, Bezier(0, -1, 0));
347
348 Bezier reverse_wiggle = reverse(wiggle);
349 EXPECT_EQ(reverse_wiggle.at0(), wiggle.at1());
350 EXPECT_EQ(reverse_wiggle.at1(), wiggle.at0());
351 EXPECT_TRUE(are_equal(reverse(reverse_wiggle), wiggle));
352
353 //cout << "Bezier portion(const Bezier & a, double from, double to);\n";
354 //cout << portion(Bezier(0.0,2.0), 0.5, 1) << endl;
355
356// std::vector<Point> bezier_points(const D2<Bezier > & a) {
357
358 /*cout << "Bezier derivative(const Bezier & a);\n";
359 std::cout << derivative(hump) <<std::endl;
360 std::cout << integral(hump) <<std::endl;*/
361
362 EXPECT_TRUE(are_equal(derivative(integral(wiggle)), wiggle));
363 //std::cout << derivative(integral(hump)) <<std::endl;
364 expect_array((const double []){0.5}, derivative(hump).roots());
365
366 EXPECT_TRUE(bounds_fast(hump)->contains(Interval(0,hump.valueAt(0.5))));
367
368 EXPECT_EQ(Interval(0,hump.valueAt(0.5)), *bounds_exact(hump));
369
370 Interval tight_local_bounds(min(hump.valueAt(0.3),hump.valueAt(0.6)),
371 hump.valueAt(0.5));
372 EXPECT_TRUE(bounds_local(hump, Interval(0.3, 0.6))->contains(tight_local_bounds));
373
374 Bezier Bs[] = {unit, hump, wiggle};
375 for(auto B : Bs) {
376 Bezier product = multiply(B, B);
377 for(int i = 0; i <= 16; i++) {
378 double t = i/16.0;
379 double b = B.valueAt(t);
380 EXPECT_near(b*b, product.valueAt(t), 1e-12);
381 }
382 }
383}
384
385struct XPt {
386 XPt(Coord x, Coord y, Coord ta, Coord tb)
387 : p(x, y), ta(ta), tb(tb)
388 {}
389 XPt() {}
390 Point p;
391 Coord ta, tb;
392};
393
394struct XTest {
395 D2<Bezier> a;
396 D2<Bezier> b;
397 std::vector<XPt> s;
398};
399
400struct CILess {
401 bool operator()(CurveIntersection const &a, CurveIntersection const &b) const {
402 if (a.first < b.first) return true;
403 if (a.first == b.first && a.second < b.second) return true;
404 return false;
405 }
406};
407
408TEST_F(BezierTest, Intersection) {
409 /* Intersection test cases taken from:
410 * Dieter Lasser (1988), Calculating the Self-Intersections of Bezier Curves
411 * https://archive.org/stream/calculatingselfi00lass
412 *
413 * The intersection points are not actually calculated to a high precision
414 * in the paper. The most relevant tests are whether the curves actually
415 * intersect at the returned time values (i.e. whether a(ta) = b(tb))
416 * and whether the number of intersections is correct.
417 */
418 typedef D2<Bezier> D2Bez;
419 std::vector<XTest> tests;
420
421 // Example 1
422 tests.emplace_back();
423 tests.back().a = D2Bez(Bezier(-3.3, -3.3, 0, 3.3, 3.3), Bezier(1.3, -0.7, 2.3, -0.7, 1.3));
424 tests.back().b = D2Bez(Bezier(-4.0, -4.0, 0, 4.0, 4.0), Bezier(-0.35, 3.0, -2.6, 3.0, -0.35));
425 tests.back().s.resize(4);
426 tests.back().s[0] = XPt(-3.12109, 0.76362, 0.09834, 0.20604);
427 tests.back().s[1] = XPt(-1.67341, 0.60298, 0.32366, 0.35662);
428 tests.back().s[2] = XPt(1.67341, 0.60298, 0.67634, 0.64338);
429 tests.back().s[3] = XPt(3.12109, 0.76362, 0.90166, 0.79396);
430
431 // Example 2
432 tests.emplace_back();
433 tests.back().a = D2Bez(Bezier(0, 0, 3, 3), Bezier(0, 14, -9, 5));
434 tests.back().b = D2Bez(Bezier(-1, 13, -10, 4), Bezier(4, 4, 1, 1));
435 tests.back().s.resize(9);
436 tests.back().s[0] = XPt(0.00809, 1.17249, 0.03029, 0.85430);
437 tests.back().s[1] = XPt(0.02596, 1.97778, 0.05471, 0.61825);
438 tests.back().s[2] = XPt(0.17250, 3.99191, 0.14570, 0.03029);
439 tests.back().s[3] = XPt(0.97778, 3.97404, 0.38175, 0.05471);
440 tests.back().s[4] = XPt(1.5, 2.5, 0.5, 0.5);
441 tests.back().s[5] = XPt(2.02221, 1.02596, 0.61825, 0.94529);
442 tests.back().s[6] = XPt(2.82750, 1.00809, 0.85430, 0.96971);
443 tests.back().s[7] = XPt(2.97404, 3.02221, 0.94529, 0.38175);
444 tests.back().s[8] = XPt(2.99191, 3.82750, 0.96971, 0.14570);
445
446 // Example 3
447 tests.emplace_back();
448 tests.back().a = D2Bez(Bezier(-5, -5, -3, 0, 3, 5, 5), Bezier(0, 3.555, -1, 4.17, -1, 3.555, 0));
449 tests.back().b = D2Bez(Bezier(-6, -6, -3, 0, 3, 6, 6), Bezier(3, -0.555, 4, -1.17, 4, -0.555, 3));
450 tests.back().s.resize(6);
451 tests.back().s[0] = XPt(-3.64353, 1.49822, 0.23120, 0.27305);
452 tests.back().s[1] = XPt(-2.92393, 1.50086, 0.29330, 0.32148);
453 tests.back().s[2] = XPt(-0.77325, 1.49989, 0.44827, 0.45409);
454 tests.back().s[3] = XPt(0.77325, 1.49989, 0.55173, 0.54591);
455 tests.back().s[4] = XPt(2.92393, 1.50086, 0.70670, 0.67852);
456 tests.back().s[5] = XPt(3.64353, 1.49822, 0.76880, 0.72695);
457
458 // Example 4
459 tests.emplace_back();
460 tests.back().a = D2Bez(Bezier(-4, -10, -2, -2, 2, 2, 10, 4), Bezier(0, 6, 6, 0, 0, 6, 6, 0));
461 tests.back().b = D2Bez(Bezier(-8, 0, 8), Bezier(1, 6, 1));
462 tests.back().s.resize(4);
463 tests.back().s[0] = XPt(-5.69310, 2.23393, 0.06613, 0.14418);
464 tests.back().s[1] = XPt(-2.68113, 3.21920, 0.35152, 0.33243);
465 tests.back().s[2] = XPt(2.68113, 3.21920, 0.64848, 0.66757);
466 tests.back().s[3] = XPt(5.69310, 2.23393, 0.93387, 0.85582);
467
468 //std::cout << std::setprecision(5);
469
470 for (unsigned i = 0; i < tests.size(); ++i) {
471 BezierCurve a(tests[i].a), b(tests[i].b);
472 std::vector<CurveIntersection> xs;
473 xs = a.intersect(b, 1e-8);
474 std::sort(xs.begin(), xs.end(), CILess());
475 //xs.erase(std::unique(xs.begin(), xs.end(), XEqual()), xs.end());
476
477 std::cout << "\n\n"
478 << "===============================\n"
479 << "=== Intersection Testcase " << i+1 << " ===\n"
480 << "===============================\n" << std::endl;
481
482 EXPECT_EQ(xs.size(), tests[i].s.size());
483 //if (xs.size() != tests[i].s.size()) continue;
484
485 for (unsigned j = 0; j < std::min(xs.size(), tests[i].s.size()); ++j) {
486 std::cout << xs[j].first << " = " << a.pointAt(xs[j].first) << " "
487 << xs[j].second << " = " << b.pointAt(xs[j].second) << "\n"
488 << tests[i].s[j].ta << " = " << tests[i].a.valueAt(tests[i].s[j].ta) << " "
489 << tests[i].s[j].tb << " = " << tests[i].b.valueAt(tests[i].s[j].tb) << std::endl;
490 }
491
492 EXPECT_intersections_valid(a, b, xs, 1e-6);
493 }
494
495 #if 0
496 // these contain second-order intersections
497 Coord a5x[] = {-1.5, -1.5, -10, -10, 0, 10, 10, 1.5, 1.5};
498 Coord a5y[] = {0, -8, -8, 9, 9, 9, -8, -8, 0};
499 Coord b5x[] = {-3, -12, 0, 12, 3};
500 Coord b5y[] = {-5, 8, 2.062507, 8, -5};
501 Coord p5x[] = {-3.60359, -5.44653, 0, 5.44653, 3.60359};
502 Coord p5y[] = {-4.10631, -0.76332, 4.14844, -0.76332, -4.10631};
503 Coord p5ta[] = {0.01787, 0.10171, 0.5, 0.89829, 0.98213};
504 Coord p5tb[] = {0.12443, 0.28110, 0.5, 0.71890, 0.87557};
505
506 Coord a6x[] = {5, 14, 10, -12, -12, -2};
507 Coord a6y[] = {1, 6, -6, -6, 2, 2};
508 Coord b6x[] = {0, 2, -10.5, -10.5, 3.5, 3, 8, 6};
509 Coord b6y[] = {0, -8, -8, 9, 9, -4.129807, -4.129807, 3};
510 Coord p6x[] = {6.29966, 5.87601, 0.04246, -4.67397, -3.57214};
511 Coord p6y[] = {1.63288, -0.86192, -2.38219, -2.17973, 1.91463};
512 Coord p6ta[] = {0.03184, 0.33990, 0.49353, 0.62148, 0.96618};
513 Coord p6tb[] = {0.96977, 0.85797, 0.05087, 0.28232, 0.46102};
514 #endif
515}
516
518TEST_F(BezierTest, QuadraticIntersectLineSeg)
519{
520 double const EPS = 1e-12;
521 auto const bow = QuadraticBezier({0, 0}, {1, 1}, {2, 0});
522 auto const highhoriz = LineSegment(Point(0, 0), Point(2, 0));
523 auto const midhoriz = LineSegment(Point(0, 0.25), Point(2, 0.25));
524 auto const lowhoriz = LineSegment(Point(0, 0.5), Point(2, 0.5));
525 auto const noninters = LineSegment(Point(0, 0.5 + EPS), Point(2, 0.5 + EPS));
526 auto const noninters2 = LineSegment(Point(1, 0), Point(1, 0.5 - EPS));
527
528 auto const endpoint_intersections = bow.intersect(highhoriz, EPS);
529 EXPECT_EQ(endpoint_intersections.size(), 2);
530 EXPECT_intersections_valid(bow, highhoriz, endpoint_intersections, EPS);
531 for (auto const &ex : endpoint_intersections) {
532 EXPECT_DOUBLE_EQ(ex.point()[Y], 0.0);
533 }
534
535 auto const mid_intersections = bow.intersect(midhoriz, EPS);
536 EXPECT_EQ(mid_intersections.size(), 2);
537 EXPECT_intersections_valid(bow, midhoriz, mid_intersections, EPS);
538 for (auto const &mx : mid_intersections) {
539 EXPECT_DOUBLE_EQ(mx.point()[Y], 0.25);
540 }
541
542 auto const tangent_intersection = bow.intersect(lowhoriz, EPS);
543 EXPECT_EQ(tangent_intersection.size(), 1);
544 EXPECT_intersections_valid(bow, lowhoriz, tangent_intersection, EPS);
545 for (auto const &tx : tangent_intersection) {
546 EXPECT_DOUBLE_EQ(tx.point()[Y], 0.5);
547 }
548
549 auto no_intersections = bow.intersect(noninters, EPS);
550 EXPECT_TRUE(no_intersections.empty());
551
552 no_intersections = bow.intersect(noninters2, EPS);
553 EXPECT_TRUE(no_intersections.empty());
554}
555
556TEST_F(BezierTest, QuadraticIntersectLineRandom)
557{
558 g_random_set_seed(0xB747A380);
559 auto const diagonal = LineSegment(Point(0, 0), Point(1, 1));
560 double const EPS = 1e-12;
561
562 for (unsigned i = 0; i < 10'000; i++) {
563 auto q = QuadraticBezier({0, 1}, {g_random_double_range(0.0, 1.0), g_random_double_range(0.0, 1.0)}, {1, 0});
564 auto xings = q.intersect(diagonal, EPS);
565 ASSERT_EQ(xings.size(), 1);
566 auto pt = xings[0].point();
567 EXPECT_TRUE(are_near(pt[X], pt[Y], EPS));
568 EXPECT_intersections_valid(q, diagonal, xings, EPS);
569 }
570}
571
573TEST_F(BezierTest, CubicIntersectLine)
574{
575 double const EPS = 1e-12;
576 auto const wavelet = CubicBezier({0, 0}, {1, 2}, {0, -2}, {1, 0});
577
578 auto const unit_seg = LineSegment(Point(0, 0), Point(1, 0));
579 auto const expect3 = wavelet.intersect(unit_seg, EPS);
580 EXPECT_EQ(expect3.size(), 3);
581 EXPECT_intersections_valid(wavelet, unit_seg, expect3, EPS);
582
583 auto const half_seg = LineSegment(Point(0, 0), Point(0.5, 0));
584 auto const expect2 = wavelet.intersect(half_seg, EPS);
585 EXPECT_EQ(expect2.size(), 2);
586 EXPECT_intersections_valid(wavelet, half_seg, expect2, EPS);
587
588 auto const less_than_half = LineSegment(Point(0, 0), Point(0.5 - EPS, 0));
589 auto const expect1 = wavelet.intersect(less_than_half, EPS);
590 EXPECT_EQ(expect1.size(), 1);
591 EXPECT_intersections_valid(wavelet, less_than_half, expect1, EPS);
592
593 auto const dollar_stroke = LineSegment(Point(0, 0.5), Point(1, -0.5));
594 auto const dollar_xings = wavelet.intersect(dollar_stroke, EPS);
595 EXPECT_EQ(dollar_xings.size(), 3);
596 EXPECT_intersections_valid(wavelet, dollar_stroke, dollar_xings, EPS);
597}
598
599TEST_F(BezierTest, CubicIntersectLineRandom)
600{
601 g_random_set_seed(0xCAFECAFE);
602 auto const diagonal = LineSegment(Point(0, 0), Point(1, 1));
603 double const EPS = 1e-8;
604
605 for (unsigned i = 0; i < 10'000; i++) {
606 double a1 = g_random_double_range(0.0, 1.0);
607 double a2 = g_random_double_range(a1, 1.0);
608 double b1 = g_random_double_range(0.0, 1.0);
609 double b2 = g_random_double_range(0.0, b1);
610
611 auto c = CubicBezier({0, 1}, {a1, a2}, {b1, b2}, {1, 0});
612 auto xings = c.intersect(diagonal, EPS);
613 ASSERT_EQ(xings.size(), 1);
614 auto pt = xings[0].point();
615 EXPECT_TRUE(are_near(pt[X], pt[Y], EPS));
616 EXPECT_intersections_valid(c, diagonal, xings, EPS);
617 }
618}
619
621TEST_F(BezierTest, Balloon)
622{
623 auto const loop = CubicBezier({0, 0}, {4, -2}, {4, 2}, {0, 0});
624 auto const seghoriz = LineSegment(Point(-1, 0), Point(0, 0));
625
626 for (double EPS : {1e-6, 1e-9, 1e-12}) {
627 // We expect that 2 intersections are found: one at each end of the loop,
628 // both at the coordinates (0, 0).
629 auto xings_horiz = loop.intersect(seghoriz, EPS);
630 EXPECT_EQ(xings_horiz.size(), 2);
631 EXPECT_intersections_valid(loop, seghoriz, xings_horiz, EPS);
632 }
633}
634
635TEST_F(BezierTest, ExpandToTransformedTest)
636{
637 auto test_curve = [] (Curve const &c) {
638 constexpr int N = 50;
639 for (int i = 0; i < N; i++) {
640 auto angle = 2 * M_PI * i / N;
641 auto transform = Affine(Rotate(angle));
642
643 auto copy = std::unique_ptr<Curve>(c.duplicate());
644 *copy *= transform;
645 auto box1 = copy->boundsExact();
646
647 auto pt = c.initialPoint() * transform;
648 auto box2 = Rect(pt, pt);
649 c.expandToTransformed(box2, transform);
650
651 for (auto i : { X, Y }) {
652 EXPECT_DOUBLE_EQ(box1[i].min(), box2[i].min());
653 EXPECT_DOUBLE_EQ(box1[i].max(), box2[i].max());
654 }
655 }
656 };
657
658 test_curve(LineSegment(Point(-1, 0), Point(1, 2)));
659 test_curve(QuadraticBezier(Point(-1, 0), Point(1, 1), Point(3, 0)));
660 test_curve(CubicBezier(Point(-1, 0), Point(1, 1), Point(2, -2), Point(3, 0)));
661}
662
663TEST_F(BezierTest, TimesWithRadiusOfCurvature)
664{
665 auto test_curve = [](BezierCurve const &curve, double radius, std::vector<Coord> times_expected) {
666 auto const times_actual = curve.timesWithRadiusOfCurvature(radius);
667 EXPECT_vector_near(times_actual, times_expected, 1e-8);
668 };
669
670 test_curve(LineSegment(Point(-1, 0), Point(1, 2)), 1.7, {});
671 test_curve(LineSegment(Point(-1, 0), Point(1, 2)), -1.7, {});
672 test_curve(QuadraticBezier(Point(-1, 0), Point(0, 1), Point(1, 0)), 1.7, {});
673 test_curve(QuadraticBezier(Point(-1, 0), Point(0, 1), Point(1, 0)), -1.7, {0.17426923333331537, 1-0.17426923333331537});
674 test_curve(CubicBezier(Point(-1, 0), Point(1, -1), Point(-1, 1), Point(1, 0)), 1.7, {0.122157669319538, 0.473131422069614});
675 test_curve(CubicBezier(Point(-1, 0), Point(1, -1), Point(-1, 1), Point(1, 0)), -1.7, {1 - 0.473131422069614, 1 - 0.122157669319538});
676 test_curve(CubicBezier(Point(-1, 0), Point(1, -2), Point(-2, -1), Point(1, 0)), 1.7, {});
677 test_curve(CubicBezier(Point(-1, 0), Point(1, -2), Point(-2, -1), Point(1, 0)), -1.7, {0.16316709499671345, 0.82043191574008589});
678 // degenerate cases, cubic representation of a point / line
679 test_curve(CubicBezier(Point(1, 0), Point(1, 0), Point(1, 0), Point(1, 0)), 1.7, {});
680 test_curve(CubicBezier(Point(1, 1), Point(2, 2), Point(1, 1), Point(2, 2)), -1.7, {});
681 test_curve(CubicBezier(Point(1, 0), Point(1, 0), Point(1, 0), Point(2, 0)), 1.7, {});
682}
683
684TEST_F(BezierTest, ForwardDifferenceTest)
685{
686 auto b = Bezier(3, 4, 2, -5, 7);
687 EXPECT_EQ(b.forward_difference(1), Bezier(19, 34, 22, 5));
688 EXPECT_EQ(b.forward_difference(2), Bezier(-3, 2, 2));
689}
690
691TEST_F(BezierTest, Coincident)
692{
693 auto const b1 = Geom::CubicBezier({0, 0}, {1, 0}, {2, 0}, {3, 0});
694 auto const b2 = Geom::CubicBezier({0, 0}, {1, 1e-9}, {2, 0}, {3, 0});
695 auto const b3 = Geom::CubicBezier({0, 0}, {1, 1e-9}, {2, -1e-9}, {3, 0});
696 auto const b1r = Geom::CubicBezier({3, 0}, {2, 0}, {1, 0}, {0, 0});
697
698 // Exactly coincident - no intersections.
699 EXPECT_EQ(b1.intersect(b1).size(), 0);
700 EXPECT_EQ(b1r.intersect(b1).size(), 0);
701
702 // Approximately coincident - should still have intersections.
703 EXPECT_EQ(b1.intersect(b2).size(), 2);
704 EXPECT_EQ(b1.intersect(b3).size(), 3);
705 EXPECT_EQ(b1r.intersect(b2).size(), 2);
706 // ASSERT_EQ(b1r.intersect(b3).size(), 3); // Fails, outputs 4.
707}
708
709TEST_F(BezierTest, InfiniteRecursion)
710{
711 auto const b = Geom::Bezier{-0.0030759119071035457, -0.0030759119071035457, 0.32441359420920435, -9.612067618408332};
712 EXPECT_EQ(b.roots().size(), 0);
713}
714
715/*
716 Local Variables:
717 mode:c++
718 c-file-style:"stroustrup"
719 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
720 indent-tabs-mode:nil
721 fill-column:99
722 End:
723*/
724// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
int test()
Definition 2junctions.cpp:5
Basic intersection routines.
Bezier curve.
Poly lin_poly(double a, double b)
Bezier linear_root(double t)
TEST_F(BezierTest, Basics)
bool are_equal(Bezier A, Bezier B)
Bezier array_roots(vector< double > x)
Bernstein-Bezier polynomial.
3x3 matrix representing an affine transformation.
Definition affine.h:70
std::vector< CurveIntersection > intersect(Curve const &other, Coord eps=EPSILON) const override
Compute intersections with another curve.
Two-dimensional Bezier curve of arbitrary order.
unsigned size() const
Get the number of control points.
Polynomial in Bernstein-Bezier basis.
Definition bezier.h:126
Bezier deflate() const
Definition bezier.cpp:176
std::vector< Coord > valueAndDerivatives(Coord t, unsigned n_derivs) const
Definition bezier.cpp:51
Bezier elevate_to_degree(unsigned newDegree) const
Definition bezier.cpp:167
void subdivide(Coord t, Bezier *left, Bezier *right) const
Definition bezier.cpp:79
std::vector< Coord > roots() const
Definition bezier.cpp:105
Bezier elevate_degree() const
Definition bezier.cpp:138
unsigned size() const
Definition bezier.h:147
Coord at0() const
Definition bezier.h:272
unsigned order() const
Definition bezier.h:145
Coord valueAt(double t) const
Definition bezier.h:277
void setCoeff(unsigned ix, double val)
Definition bezier.h:287
Coord at1() const
Definition bezier.h:274
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
Intersection between two shapes.
TimeB second
Second shape and time value.
TimeA first
First shape and time value.
Range of real numbers that is never empty.
Definition interval.h:59
Range of real numbers that can be empty.
Definition interval.h:199
Two-dimensional point that doubles as a vector.
Definition point.h:66
Polynomial in canonical (monomial) basis.
Definition polynomial.h:50
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotation around the origin.
Definition transforms.h:187
Css & result
double c[8][4]
const unsigned order
BezierCurveN< 3 > CubicBezier
Cubic (order 3) Bezier curve.
BezierCurveN< 1 > LineSegment
Line segment.
BezierCurveN< 2 > QuadraticBezier
Quadratic (order 2) Bezier curve.
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Various utility functions.
Definition affine.h:22
OptInterval bounds_exact(Bezier const &b)
Definition bezier.cpp:310
Bezier reverse(const Bezier &a)
Definition bezier.h:342
OptInterval bounds_local(Bezier const &b, OptInterval const &i)
Definition bezier.cpp:320
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
Definition sbasisN.h:158
Bezier multiply(Bezier const &f, Bezier const &g)
Definition bezier.h:337
bool contains(Path const &p, Point const &i, bool evenodd=true)
std::string format_coord_nice(Coord x)
Definition coord.cpp:89
Bezier portion(const Bezier &a, double from, double to)
Definition bezier.cpp:250
Bezier integral(Bezier const &a)
Definition bezier.cpp:294
Bezier derivative(Bezier const &a)
Definition bezier.cpp:282
T bernstein_value_at(double t, T const *c_, unsigned n)
Compute the value of a Bernstein-Bezier polynomial.
Definition bezier.h:55
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
OptInterval bounds_fast(Bezier const &b)
Definition bezier.cpp:305
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Polynomial in canonical (monomial) basis.
size_t N
int test_curve(void)
Definition spiro.cpp:1075
Definition curve.h:24
void expect_array(const T(&x)[xn], std::vector< T > y)
Definition testing.h:31
std::vector< T > vector_from_array(const T(&x)[xn])
Definition testing.h:22