Code

enable safe support for motion hints
[inkscape.git] / src / display / bezier-utils-test.h
1 #include <cxxtest/TestSuite.h>
3 #include <glib.h>
4 #include <libnr/nr-macros.h> /* NR_DF_TEST_CLOSE */
5 #include <sstream>
7 /* mental disclaims all responsibility for this evil idea for testing
8    static functions.  The main disadvantages are that we retain the
9    #define's and `using' directives of the included file. */
10 #include "bezier-utils.cpp"
12 using NR::Point;
14 /* (Returns false if NaN encountered.) */
15 static bool range_approx_equal(double const a[], double const b[], unsigned const len) {
16     for (unsigned i = 0; i < len; ++i) {
17         if (!( fabs( a[i] - b[i] ) < 1e-4 )) {
18             return false;
19         }
20     }
21     return true;
22 }
24 static inline bool point_approx_equal(NR::Point const &a, NR::Point const &b, double const eps)
25 {
26     using NR::X; using NR::Y;
27     return ( NR_DF_TEST_CLOSE(a[X], b[X], eps) &&
28              NR_DF_TEST_CLOSE(a[Y], b[Y], eps) );
29 }
31 static inline double square(double const x) {
32     return x * x;
33 }
35 /** Determine whether the found control points are the same as previously found on some developer's
36     machine.  Doesn't call utest__fail, just writes a message to stdout for diagnostic purposes:
37     the most important test is that the root-mean-square of errors in the estimation are low rather
38     than that the control points found are the same.
39 **/
40 static void compare_ctlpts(Point const est_b[], Point const exp_est_b[])
41 {
42     unsigned diff_mask = 0;
43     for (unsigned i = 0; i < 4; ++i) {
44         for (unsigned d = 0; d < 2; ++d) {
45             if ( fabs( est_b[i][d] - exp_est_b[i][d] ) > 1.1e-5 ) {
46                 diff_mask |= 1 << ( i * 2 + d );
47             }
48         }
49     }
50     if ( diff_mask != 0 ) {
51         std::stringstream msg;
52         msg << "Got different control points from previously-coded (diffs=0x" << std::hex << diff_mask << "\n";
53         msg << " Previous:";
54         for (unsigned i = 0; i < 4; ++i) {
55             msg << " (" << exp_est_b[i][0] << ", " << exp_est_b[i][1] << ")"; // localizing ok
56         }
57         msg << "\n";
58         msg << " Found:   ";
59         for (unsigned i = 0; i < 4; ++i) {
60             msg << " (" << est_b[i][0] << ", " << est_b[i][1] << ")"; // localizing ok
61         }
62         msg << "\n";
63         TS_WARN(msg.str().c_str());
64     }
65 }
67 static void compare_rms(Point const est_b[], double const t[], Point const d[], unsigned const n,
68                         double const exp_rms_error)
69 {
70     double sum_errsq = 0.0;
71     for (unsigned i = 0; i < n; ++i) {
72         Point const fit_pt = bezier_pt(3, est_b, t[i]);
73         Point const diff = fit_pt - d[i];
74         sum_errsq += dot(diff, diff);
75     }
76     double const rms_error = sqrt( sum_errsq / n );
77     TS_ASSERT_LESS_THAN_EQUALS( rms_error , exp_rms_error + 1.1e-6 );
78     if ( rms_error < exp_rms_error - 1.1e-6 ) {
79         /* The fitter code appears to have improved [or the floating point calculations differ
80            on this machine from the machine where exp_rms_error was calculated]. */
81         char msg[200];
82         sprintf(msg, "N.B. rms_error regression requirement can be decreased: have rms_error=%g.", rms_error); // localizing ok
83         TS_TRACE(msg);
84     }
85 }
87 class BezierUtilsTest : public CxxTest::TestSuite {
88 public:
89     static Point const c[4];
90     static double const t[24];
91     static unsigned const n;
92     Point d[24];
93     static Point const src_b[4];
94     static Point const tHat1;
95     static Point const tHat2;
97     BezierUtilsTest()
98     {
99         /* Feed it some points that can be fit exactly with a single bezier segment, and see how
100         well it manages. */
101         for (unsigned i = 0; i < n; ++i) {
102             d[i] = bezier_pt(3, src_b, t[i]);
103         }
104     }
105     virtual ~BezierUtilsTest() {}
107     void testCopyWithoutNansOrAdjacentDuplicates()
108     {
109         NR::Point const src[] = {
110             Point(2., 3.),
111             Point(2., 3.),
112             Point(0., 0.),
113             Point(2., 3.),
114             Point(2., 3.),
115             Point(1., 9.),
116             Point(1., 9.)
117         };
118         Point const exp_dest[] = {
119             Point(2., 3.),
120             Point(0., 0.),
121             Point(2., 3.),
122             Point(1., 9.)
123         };
124         g_assert( G_N_ELEMENTS(src) == 7 );
125         Point dest[7];
126         struct tst {
127             unsigned src_ix0;
128             unsigned src_len;
129             unsigned exp_dest_ix0;
130             unsigned exp_dest_len;
131         } const test_data[] = {
132             /* src start ix, src len, exp_dest start ix, exp dest len */
133             {0, 0, 0, 0},
134             {2, 1, 1, 1},
135             {0, 1, 0, 1},
136             {0, 2, 0, 1},
137             {0, 3, 0, 2},
138             {1, 3, 0, 3},
139             {0, 5, 0, 3},
140             {0, 6, 0, 4},
141             {0, 7, 0, 4}
142         };
143         for (unsigned i = 0 ; i < G_N_ELEMENTS(test_data) ; ++i) {
144             tst const &t = test_data[i];
145             TS_ASSERT_EQUALS( t.exp_dest_len,
146                               copy_without_nans_or_adjacent_duplicates(src + t.src_ix0,
147                                                                        t.src_len,
148                                                                        dest) );
149             TS_ASSERT_SAME_DATA(dest,
150                                 exp_dest + t.exp_dest_ix0,
151                                 t.exp_dest_len);
152         }
153     }
155     void testBezierPt1()
156     {
157         Point const a[] = {Point(2.0, 4.0),
158                            Point(1.0, 8.0)};
159         TS_ASSERT_EQUALS( bezier_pt(1, a, 0.0) , a[0] );
160         TS_ASSERT_EQUALS( bezier_pt(1, a, 1.0) , a[1] );
161         TS_ASSERT_EQUALS( bezier_pt(1, a, 0.5) , Point(1.5, 6.0) );
162         double const t[] = {0.5, 0.25, 0.3, 0.6};
163         for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
164             double const ti = t[i], si = 1.0 - ti;
165             TS_ASSERT_EQUALS( bezier_pt(1, a, ti) , si * a[0] + ti * a[1] );
166         }
167     }
169     void testBezierPt2()
170     {
171         Point const b[] = {Point(1.0, 2.0),
172                            Point(8.0, 4.0),
173                            Point(3.0, 1.0)};
174         TS_ASSERT_EQUALS( bezier_pt(2, b, 0.0) , b[0] );
175         TS_ASSERT_EQUALS( bezier_pt(2, b, 1.0) , b[2] );
176         TS_ASSERT_EQUALS( bezier_pt(2, b, 0.5) , Point(5.0, 2.75) );
177         double const t[] = {0.5, 0.25, 0.3, 0.6};
178         for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
179             double const ti = t[i], si = 1.0 - ti;
180             Point const exp_pt( si*si * b[0] + 2*si*ti * b[1] + ti*ti * b[2] );
181             Point const pt(bezier_pt(2, b, ti));
182             TS_ASSERT(point_approx_equal(pt, exp_pt, 1e-11));
183         }
184     }
186     void testBezierPt3()
187     {
188         TS_ASSERT_EQUALS( bezier_pt(3, c, 0.0) , c[0] );
189         TS_ASSERT_EQUALS( bezier_pt(3, c, 1.0) , c[3] );
190         TS_ASSERT_EQUALS( bezier_pt(3, c, 0.5) , Point(4.0, 13.0/8.0) );
191         double const t[] = {0.5, 0.25, 0.3, 0.6};
192         for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
193             double const ti = t[i], si = 1.0 - ti;
194             TS_ASSERT( LInfty( bezier_pt(3, c, ti)
195                                   - ( si*si*si * c[0] +
196                                       3*si*si*ti * c[1] +
197                                       3*si*ti*ti * c[2] +
198                                       ti*ti*ti * c[3] ) )
199                           < 1e-4 );
200         }
201     }
203     void testComputeMaxErrorRatio()
204     {
205         struct Err_tst {
206             Point pt;
207             double u;
208             double err;
209         } const err_tst[] = {
210             {c[0], 0.0, 0.0},
211             {Point(4.0, 13.0/8.0), 0.5, 0.0},
212             {Point(4.0, 2.0), 0.5, 9.0/64.0},
213             {Point(3.0, 2.0), 0.5, 1.0 + 9.0/64.0},
214             {Point(6.0, 2.0), 0.5, 4.0 + 9.0/64.0},
215             {c[3], 1.0, 0.0},
216         };
217         Point d[G_N_ELEMENTS(err_tst)];
218         double u[G_N_ELEMENTS(err_tst)];
219         for (unsigned i = 0; i < G_N_ELEMENTS(err_tst); ++i) {
220             Err_tst const &t = err_tst[i];
221             d[i] = t.pt;
222             u[i] = t.u;
223         }
224         g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(d) );
225         unsigned max_ix = ~0u;
226         double const err_ratio = compute_max_error_ratio(d, u, G_N_ELEMENTS(d), c, 1.0, &max_ix);
227         TS_ASSERT_LESS_THAN( fabs( sqrt(err_tst[4].err) - err_ratio ) , 1e-12 );
228         TS_ASSERT_EQUALS( max_ix , 4 );
229     }
231     void testChordLengthParameterize()
232     {
233         /* n == 2 */
234         {
235             Point const d[] = {Point(2.9415, -5.8149),
236                                Point(23.021, 4.9814)};
237             double u[G_N_ELEMENTS(d)];
238             double const exp_u[] = {0.0, 1.0};
239             g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(exp_u) );
240             chord_length_parameterize(d, u, G_N_ELEMENTS(d));
241             TS_ASSERT_SAME_DATA(u, exp_u, G_N_ELEMENTS(exp_u));
242         }
244         /* Straight line. */
245         {
246             double const exp_u[] = {0.0, 0.1829, 0.2105, 0.2105, 0.619, 0.815, 0.999, 1.0};
247             unsigned const n = G_N_ELEMENTS(exp_u);
248             Point d[n];
249             double u[n];
250             Point const a(-23.985, 4.915), b(4.9127, 5.203);
251             for (unsigned i = 0; i < n; ++i) {
252                 double bi = exp_u[i], ai = 1.0 - bi;
253                 d[i] = ai * a  +  bi * b;
254             }
255             chord_length_parameterize(d, u, n);
256             TS_ASSERT(range_approx_equal(u, exp_u, n));
257         }
258     }
260     void testGenerateBezier()
261     {
262         Point est_b[4];
263         generate_bezier(est_b, d, t, n, tHat1, tHat2, 1.0);
265         compare_ctlpts(est_b, src_b);
267         /* We're being unfair here in using our t[] rather than best t[] for est_b: we
268            may over-estimate RMS of errors. */
269         compare_rms(est_b, t, d, n, 1e-8);
270     }
272     void testSpBezierFitCubicFull()
273     {
274         Point est_b[4];
275         int splitpoints[2];
276         gint const succ = sp_bezier_fit_cubic_full(est_b, splitpoints, d, n, tHat1, tHat2, square(1.2), 1);
277         TS_ASSERT_EQUALS( succ , 1 );
279         Point const exp_est_b[4] = {
280             Point(5.000000, -3.000000),
281             Point(7.5753, -0.4247),
282             Point(4.77533, 1.22467),
283             Point(3, 3)
284         };
285         compare_ctlpts(est_b, exp_est_b);
287         /* We're being unfair here in using our t[] rather than best t[] for est_b: we
288            may over-estimate RMS of errors. */
289         compare_rms(est_b, t, d, n, .307911);
290     }
292     void testSpBezierFitCubic()
293     {
294         Point est_b[4];
295         gint const succ = sp_bezier_fit_cubic(est_b, d, n, square(1.2));
296         TS_ASSERT_EQUALS( succ , 1 );
298         Point const exp_est_b[4] = {
299             Point(5.000000, -3.000000),
300             Point(7.57134, -0.423509),
301             Point(4.77929, 1.22426),
302             Point(3, 3)
303         };
304         compare_ctlpts(est_b, exp_est_b);
306 #if 1 /* A change has been made to right_tangent.  I believe that usually this change
307          will result in better fitting, but it won't do as well for this example where
308          we happen to be feeding a t=0.999 point to the fitter. */
309         TS_WARN("TODO: Update this test case for revised right_tangent implementation.");
310         /* In particular, have a test case to show whether the new implementation
311            really is likely to be better on average. */
312 #else
313         /* We're being unfair here in using our t[] rather than best t[] for est_b: we
314            may over-estimate RMS of errors. */
315         compare_rms(est_b, t, d, n, .307983);
316 #endif
317     }
318 };
320 // This is not very neat, but since we know this header is only included by the generated CxxTest file it shouldn't give any problems
321 Point const BezierUtilsTest::c[4] = {
322     Point(1.0, 2.0),
323     Point(8.0, 4.0),
324     Point(3.0, 1.0),
325     Point(-2.0, -4.0)};
326 double const BezierUtilsTest::t[24] = {
327     0.0, .001, .03, .05, .09, .13, .18, .25, .29, .33,  .39, .44,
328     .51,  .57, .62, .69, .75, .81, .91, .93, .97, .98, .999, 1.0};
329 unsigned const BezierUtilsTest::n = G_N_ELEMENTS(BezierUtilsTest::t);
330 Point const BezierUtilsTest::src_b[4] = {
331     Point(5., -3.),
332     Point(8., 0.),
333     Point(4., 2.),
334     Point(3., 3.)};
335 Point const BezierUtilsTest::tHat1(unit_vector( BezierUtilsTest::src_b[1] - BezierUtilsTest::src_b[0] ));
336 Point const BezierUtilsTest::tHat2(unit_vector( BezierUtilsTest::src_b[2] - BezierUtilsTest::src_b[3] ));
338 /*
339   Local Variables:
340   mode:c++
341   c-file-style:"stroustrup"
342   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
343   indent-tabs-mode:nil
344   fill-column:99
345   End:
346 */
347 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :