GCC Code Coverage Report


Directory: ./
File: src/2geom/recursive-bezier-intersection.cpp
Date: 2024-03-18 17:01:34
Exec Total Coverage
Lines: 0 128 0.0%
Functions: 0 11 0.0%
Branches: 0 234 0.0%

Line Branch Exec Source
1
2
3
4 #include <2geom/basic-intersection.h>
5 #include <2geom/sbasis-to-bezier.h>
6 #include <2geom/exception.h>
7
8
9 #include <gsl/gsl_vector.h>
10 #include <gsl/gsl_multiroots.h>
11
12
13 unsigned intersect_steps = 0;
14
15 using std::vector;
16
17 namespace Geom {
18
19 class OldBezier {
20 public:
21 std::vector<Geom::Point> p;
22 OldBezier() {
23 }
24 void split(double t, OldBezier &a, OldBezier &b) const;
25 Point operator()(double t) const;
26
27 ~OldBezier() {}
28
29 void bounds(double &minax, double &maxax,
30 double &minay, double &maxay) {
31 // Compute bounding box for a
32 minax = p[0][X]; // These are the most likely to be extremal
33 maxax = p.back()[X];
34 if( minax > maxax )
35 std::swap(minax, maxax);
36 for(unsigned i = 1; i < p.size()-1; i++) {
37 if( p[i][X] < minax )
38 minax = p[i][X];
39 else if( p[i][X] > maxax )
40 maxax = p[i][X];
41 }
42
43 minay = p[0][Y]; // These are the most likely to be extremal
44 maxay = p.back()[Y];
45 if( minay > maxay )
46 std::swap(minay, maxay);
47 for(unsigned i = 1; i < p.size()-1; i++) {
48 if( p[i][Y] < minay )
49 minay = p[i][Y];
50 else if( p[i][Y] > maxay )
51 maxay = p[i][Y];
52 }
53
54 }
55
56 };
57
58 static void
59 find_intersections_bezier_recursive(std::vector<std::pair<double, double> > & xs,
60 OldBezier a,
61 OldBezier b);
62
63 void
64 find_intersections_bezier_recursive( std::vector<std::pair<double, double> > &xs,
65 vector<Geom::Point> const & A,
66 vector<Geom::Point> const & B,
67 double /*precision*/) {
68 OldBezier a, b;
69 a.p = A;
70 b.p = B;
71 return find_intersections_bezier_recursive(xs, a,b);
72 }
73
74
75 /*
76 * split the curve at the midpoint, returning an array with the two parts
77 * Temporary storage is minimized by using part of the storage for the result
78 * to hold an intermediate value until it is no longer needed.
79 */
80 void OldBezier::split(double t, OldBezier &left, OldBezier &right) const {
81 const unsigned sz = p.size();
82 //Geom::Point Vtemp[sz][sz];
83 std::vector< std::vector< Geom::Point > > Vtemp;
84 for (size_t i = 0; i < sz; ++i )
85 Vtemp[i].reserve(sz);
86
87 /* Copy control points */
88 std::copy(p.begin(), p.end(), Vtemp[0].begin());
89
90 /* Triangle computation */
91 for (unsigned i = 1; i < sz; i++) {
92 for (unsigned j = 0; j < sz - i; j++) {
93 Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]);
94 }
95 }
96
97 left.p.resize(sz);
98 right.p.resize(sz);
99 for (unsigned j = 0; j < sz; j++)
100 left.p[j] = Vtemp[j][0];
101 for (unsigned j = 0; j < sz; j++)
102 right.p[j] = Vtemp[sz-1-j][j];
103 }
104
105 #if 0
106 /*
107 * split the curve at the midpoint, returning an array with the two parts
108 * Temporary storage is minimized by using part of the storage for the result
109 * to hold an intermediate value until it is no longer needed.
110 */
111 Point OldBezier::operator()(double t) const {
112 const unsigned sz = p.size();
113 Geom::Point Vtemp[sz][sz];
114
115 /* Copy control points */
116 std::copy(p.begin(), p.end(), Vtemp[0]);
117
118 /* Triangle computation */
119 for (unsigned i = 1; i < sz; i++) {
120 for (unsigned j = 0; j < sz - i; j++) {
121 Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]);
122 }
123 }
124 return Vtemp[sz-1][0];
125 }
126 #endif
127
128 // suggested by Sederberg.
129 Point OldBezier::operator()(double const t) const {
130 size_t const n = p.size()-1;
131 Point r;
132 for(int dim = 0; dim < 2; dim++) {
133 double const u = 1.0 - t;
134 double bc = 1;
135 double tn = 1;
136 double tmp = p[0][dim]*u;
137 for(size_t i=1; i<n; i++){
138 tn = tn*t;
139 bc = bc*(n-i+1)/i;
140 tmp = (tmp + tn*bc*p[i][dim])*u;
141 }
142 r[dim] = (tmp + tn*t*p[n][dim]);
143 }
144 return r;
145 }
146
147
148 /*
149 * Test the bounding boxes of two OldBezier curves for interference.
150 * Several observations:
151 * First, it is cheaper to compute the bounding box of the second curve
152 * and test its bounding box for interference than to use a more direct
153 * approach of comparing all control points of the second curve with
154 * the various edges of the bounding box of the first curve to test
155 * for interference.
156 * Second, after a few subdivisions it is highly probable that two corners
157 * of the bounding box of a given Bezier curve are the first and last
158 * control point. Once this happens once, it happens for all subsequent
159 * subcurves. It might be worth putting in a test and then short-circuit
160 * code for further subdivision levels.
161 * Third, in the final comparison (the interference test) the comparisons
162 * should both permit equality. We want to find intersections even if they
163 * occur at the ends of segments.
164 * Finally, there are tighter bounding boxes that can be derived. It isn't
165 * clear whether the higher probability of rejection (and hence fewer
166 * subdivisions and tests) is worth the extra work.
167 */
168
169 bool intersect_BB( OldBezier a, OldBezier b ) {
170 double minax, maxax, minay, maxay;
171 a.bounds(minax, maxax, minay, maxay);
172 double minbx, maxbx, minby, maxby;
173 b.bounds(minbx, maxbx, minby, maxby);
174 // Test bounding box of b against bounding box of a
175 // Not >= : need boundary case
176 return !( ( minax > maxbx ) || ( minay > maxby )
177 || ( minbx > maxax ) || ( minby > maxay ) );
178 }
179
180 /*
181 * Recursively intersect two curves keeping track of their real parameters
182 * and depths of intersection.
183 * The results are returned in a 2-D array of doubles indicating the parameters
184 * for which intersections are found. The parameters are in the order the
185 * intersections were found, which is probably not in sorted order.
186 * When an intersection is found, the parameter value for each of the two
187 * is stored in the index elements array, and the index is incremented.
188 *
189 * If either of the curves has subdivisions left before it is straight
190 * (depth > 0)
191 * that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
192 * the depth(s) is (are) decremented, and the parameter value(s) corresponding
193 * to the midpoints(s) is (are) computed.
194 * Then each of the subcurves of one curve is intersected with each of the
195 * subcurves of the other curve, first by testing the bounding boxes for
196 * interference. If there is any bounding box interference, the corresponding
197 * subcurves are recursively intersected.
198 *
199 * If neither curve has subdivisions left, the line segments from the first
200 * to last control point of each segment are intersected. (Actually the
201 * only the parameter value corresponding to the intersection point is found).
202 *
203 * The apriori flatness test is probably more efficient than testing at each
204 * level of recursion, although a test after three or four levels would
205 * probably be worthwhile, since many curves become flat faster than their
206 * asymptotic rate for the first few levels of recursion.
207 *
208 * The bounding box test fails much more frequently than it succeeds, providing
209 * substantial pruning of the search space.
210 *
211 * Each (sub)curve is subdivided only once, hence it is not possible that for
212 * one final line intersection test the subdivision was at one level, while
213 * for another final line intersection test the subdivision (of the same curve)
214 * was at another. Since the line segments share endpoints, the intersection
215 * is robust: a near-tangential intersection will yield zero or two
216 * intersections.
217 */
218 void recursively_intersect( OldBezier a, double t0, double t1, int deptha,
219 OldBezier b, double u0, double u1, int depthb,
220 std::vector<std::pair<double, double> > &parameters)
221 {
222 intersect_steps ++;
223 //std::cout << deptha << std::endl;
224 if( deptha > 0 )
225 {
226 OldBezier A[2];
227 a.split(0.5, A[0], A[1]);
228 double tmid = (t0+t1)*0.5;
229 deptha--;
230 if( depthb > 0 )
231 {
232 OldBezier B[2];
233 b.split(0.5, B[0], B[1]);
234 double umid = (u0+u1)*0.5;
235 depthb--;
236 if( intersect_BB( A[0], B[0] ) )
237 recursively_intersect( A[0], t0, tmid, deptha,
238 B[0], u0, umid, depthb,
239 parameters );
240 if( intersect_BB( A[1], B[0] ) )
241 recursively_intersect( A[1], tmid, t1, deptha,
242 B[0], u0, umid, depthb,
243 parameters );
244 if( intersect_BB( A[0], B[1] ) )
245 recursively_intersect( A[0], t0, tmid, deptha,
246 B[1], umid, u1, depthb,
247 parameters );
248 if( intersect_BB( A[1], B[1] ) )
249 recursively_intersect( A[1], tmid, t1, deptha,
250 B[1], umid, u1, depthb,
251 parameters );
252 }
253 else
254 {
255 if( intersect_BB( A[0], b ) )
256 recursively_intersect( A[0], t0, tmid, deptha,
257 b, u0, u1, depthb,
258 parameters );
259 if( intersect_BB( A[1], b ) )
260 recursively_intersect( A[1], tmid, t1, deptha,
261 b, u0, u1, depthb,
262 parameters );
263 }
264 }
265 else
266 if( depthb > 0 )
267 {
268 OldBezier B[2];
269 b.split(0.5, B[0], B[1]);
270 double umid = (u0 + u1)*0.5;
271 depthb--;
272 if( intersect_BB( a, B[0] ) )
273 recursively_intersect( a, t0, t1, deptha,
274 B[0], u0, umid, depthb,
275 parameters );
276 if( intersect_BB( a, B[1] ) )
277 recursively_intersect( a, t0, t1, deptha,
278 B[0], umid, u1, depthb,
279 parameters );
280 }
281 else // Both segments are fully subdivided; now do line segments
282 {
283 double xlk = a.p.back()[X] - a.p[0][X];
284 double ylk = a.p.back()[Y] - a.p[0][Y];
285 double xnm = b.p.back()[X] - b.p[0][X];
286 double ynm = b.p.back()[Y] - b.p[0][Y];
287 double xmk = b.p[0][X] - a.p[0][X];
288 double ymk = b.p[0][Y] - a.p[0][Y];
289 double det = xnm * ylk - ynm * xlk;
290 if( 1.0 + det == 1.0 )
291 return;
292 else
293 {
294 double detinv = 1.0 / det;
295 double s = ( xnm * ymk - ynm *xmk ) * detinv;
296 double t = ( xlk * ymk - ylk * xmk ) * detinv;
297 if( ( s < 0.0 ) || ( s > 1.0 ) || ( t < 0.0 ) || ( t > 1.0 ) )
298 return;
299 parameters.emplace_back(t0 + s * ( t1 - t0 ),
300 u0 + t * ( u1 - u0 ));
301 }
302 }
303 }
304
305 inline double log4( double x ) { return log(x)/log(4.); }
306
307 /*
308 * Wang's theorem is used to estimate the level of subdivision required,
309 * but only if the bounding boxes interfere at the top level.
310 * Assuming there is a possible intersection, recursively_intersect is
311 * used to find all the parameters corresponding to intersection points.
312 * these are then sorted and returned in an array.
313 */
314
315 double Lmax(Point p) {
316 return std::max(fabs(p[X]), fabs(p[Y]));
317 }
318
319
320 unsigned wangs_theorem(OldBezier /*a*/) {
321 return 6; // seems a good approximation!
322
323 /*
324 const double INV_EPS = (1L<<14); // The value of 1.0 / (1L<<14) is enough for most applications
325
326 double la1 = Lmax( ( a.p[2] - a.p[1] ) - (a.p[1] - a.p[0]) );
327 double la2 = Lmax( ( a.p[3] - a.p[2] ) - (a.p[2] - a.p[1]) );
328 double l0 = std::max(la1, la2);
329 unsigned ra;
330 if( l0 * 0.75 * M_SQRT2 + 1.0 == 1.0 )
331 ra = 0;
332 else
333 ra = (unsigned)ceil( log4( M_SQRT2 * 6.0 / 8.0 * INV_EPS * l0 ) );
334 //std::cout << ra << std::endl;
335 return ra;*/
336 }
337
338 struct rparams
339 {
340 OldBezier &A;
341 OldBezier &B;
342 };
343
344 /*static int
345 intersect_polish_f (const gsl_vector * x, void *params,
346 gsl_vector * f)
347 {
348 const double x0 = gsl_vector_get (x, 0);
349 const double x1 = gsl_vector_get (x, 1);
350
351 Geom::Point dx = ((struct rparams *) params)->A(x0) -
352 ((struct rparams *) params)->B(x1);
353
354 gsl_vector_set (f, 0, dx[0]);
355 gsl_vector_set (f, 1, dx[1]);
356
357 return GSL_SUCCESS;
358 }*/
359
360 /*union dbl_64{
361 long long i64;
362 double d64;
363 };*/
364
365 /*static double EpsilonBy(double value, int eps)
366 {
367 dbl_64 s;
368 s.d64 = value;
369 s.i64 += eps;
370 return s.d64;
371 }*/
372
373 /*
374 static void intersect_polish_root (OldBezier &A, double &s,
375 OldBezier &B, double &t) {
376 const gsl_multiroot_fsolver_type *T;
377 gsl_multiroot_fsolver *sol;
378
379 int status;
380 size_t iter = 0;
381
382 const size_t n = 2;
383 struct rparams p = {A, B};
384 gsl_multiroot_function f = {&intersect_polish_f, n, &p};
385
386 double x_init[2] = {s, t};
387 gsl_vector *x = gsl_vector_alloc (n);
388
389 gsl_vector_set (x, 0, x_init[0]);
390 gsl_vector_set (x, 1, x_init[1]);
391
392 T = gsl_multiroot_fsolver_hybrids;
393 sol = gsl_multiroot_fsolver_alloc (T, 2);
394 gsl_multiroot_fsolver_set (sol, &f, x);
395
396 do
397 {
398 iter++;
399 status = gsl_multiroot_fsolver_iterate (sol);
400
401 if (status) // check if solver is stuck
402 break;
403
404 status =
405 gsl_multiroot_test_residual (sol->f, 1e-12);
406 }
407 while (status == GSL_CONTINUE && iter < 1000);
408
409 s = gsl_vector_get (sol->x, 0);
410 t = gsl_vector_get (sol->x, 1);
411
412 gsl_multiroot_fsolver_free (sol);
413 gsl_vector_free (x);
414
415 // This code does a neighbourhood search for minor improvements.
416 double best_v = L1(A(s) - B(t));
417 //std::cout << "------\n" << best_v << std::endl;
418 Point best(s,t);
419 while (true) {
420 Point trial = best;
421 double trial_v = best_v;
422 for(int nsi = -1; nsi < 2; nsi++) {
423 for(int nti = -1; nti < 2; nti++) {
424 Point n(EpsilonBy(best[0], nsi),
425 EpsilonBy(best[1], nti));
426 double c = L1(A(n[0]) - B(n[1]));
427 //std::cout << c << "; ";
428 if (c < trial_v) {
429 trial = n;
430 trial_v = c;
431 }
432 }
433 }
434 if(trial == best) {
435 //std::cout << "\n" << s << " -> " << s - best[0] << std::endl;
436 //std::cout << t << " -> " << t - best[1] << std::endl;
437 //std::cout << best_v << std::endl;
438 s = best[0];
439 t = best[1];
440 return;
441 } else {
442 best = trial;
443 best_v = trial_v;
444 }
445 }
446 }*/
447
448
449 void find_intersections_bezier_recursive( std::vector<std::pair<double, double> > &xs,
450 OldBezier a, OldBezier b)
451 {
452 if( intersect_BB( a, b ) )
453 {
454 recursively_intersect( a, 0., 1., wangs_theorem(a),
455 b, 0., 1., wangs_theorem(b),
456 xs);
457 }
458 /*for(unsigned i = 0; i < xs.size(); i++)
459 intersect_polish_root(a, xs[i].first,
460 b, xs[i].second);*/
461 std::sort(xs.begin(), xs.end());
462 }
463
464
465 };
466
467 /*
468 Local Variables:
469 mode:c++
470 c-file-style:"stroustrup"
471 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
472 indent-tabs-mode:nil
473 fill-column:99
474 End:
475 */
476 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
477