Code

add const to pointers that are not written to
[inkscape.git] / src / helper / geom.cpp
1 #define INKSCAPE_HELPER_GEOM_CPP\r
2 \r
3 /**\r
4  * Specific geometry functions for Inkscape, not provided my lib2geom.\r
5  *\r
6  * Author:\r
7  *   Johan Engelen <goejendaagh@zonnet.nl>\r
8  *\r
9  * Copyright (C) 2008 Johan Engelen\r
10  *\r
11  * Released under GNU GPL\r
12  */\r
13 \r
14 #include "helper/geom.h"\r
15 \r
16 #include <2geom/pathvector.h>\r
17 #include <2geom/path.h>\r
18 #include <2geom/transforms.h>\r
19 #include <2geom/rect.h>\r
20 #include <2geom/coord.h>\r
21 #include <2geom/sbasis-to-bezier.h>\r
22 #include <libnr/nr-convert2geom.h>\r
23 #include <glibmm.h>\r
24 \r
25 using Geom::X;\r
26 using Geom::Y;\r
27 \r
28 #define NR_HUGE   1e18\r
29 \r
30 //#################################################################################\r
31 // BOUNDING BOX CALCULATIONS\r
32 \r
33 /* Fast bbox calculation */\r
34 /* Thanks to Nathan Hurst for suggesting it */\r
35 static void\r
36 cubic_bbox (Geom::Coord x000, Geom::Coord y000, Geom::Coord x001, Geom::Coord y001, Geom::Coord x011, Geom::Coord y011, Geom::Coord x111, Geom::Coord y111, Geom::Rect &bbox)\r
37 {\r
38     Geom::Coord a, b, c, D;\r
39 \r
40     bbox[0].extendTo(x111);\r
41     bbox[1].extendTo(y111);\r
42 \r
43     /*\r
44      * xttt = s * (s * (s * x000 + t * x001) + t * (s * x001 + t * x011)) + t * (s * (s * x001 + t * x011) + t * (s * x011 + t * x111))\r
45      * xttt = s * (s2 * x000 + s * t * x001 + t * s * x001 + t2 * x011) + t * (s2 * x001 + s * t * x011 + t * s * x011 + t2 * x111)\r
46      * xttt = s * (s2 * x000 + 2 * st * x001 + t2 * x011) + t * (s2 * x001 + 2 * st * x011 + t2 * x111)\r
47      * xttt = s3 * x000 + 2 * s2t * x001 + st2 * x011 + s2t * x001 + 2st2 * x011 + t3 * x111\r
48      * xttt = s3 * x000 + 3s2t * x001 + 3st2 * x011 + t3 * x111\r
49      * xttt = s3 * x000 + (1 - s) 3s2 * x001 + (1 - s) * (1 - s) * 3s * x011 + (1 - s) * (1 - s) * (1 - s) * x111\r
50      * xttt = s3 * x000 + (3s2 - 3s3) * x001 + (3s - 6s2 + 3s3) * x011 + (1 - 2s + s2 - s + 2s2 - s3) * x111\r
51      * xttt = (x000 - 3 * x001 + 3 * x011 -     x111) * s3 +\r
52      *        (       3 * x001 - 6 * x011 + 3 * x111) * s2 +\r
53      *        (                  3 * x011 - 3 * x111) * s  +\r
54      *        (                                 x111)\r
55      * xttt' = (3 * x000 - 9 * x001 +  9 * x011 - 3 * x111) * s2 +\r
56      *         (           6 * x001 - 12 * x011 + 6 * x111) * s  +\r
57      *         (                       3 * x011 - 3 * x111)\r
58      */\r
59 \r
60     a = 3 * x000 - 9 * x001 + 9 * x011 - 3 * x111;\r
61     b = 6 * x001 - 12 * x011 + 6 * x111;\r
62     c = 3 * x011 - 3 * x111;\r
63 \r
64     /*\r
65      * s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a;\r
66      */\r
67     if (fabs (a) < Geom::EPSILON) {\r
68         /* s = -c / b */\r
69         if (fabs (b) > Geom::EPSILON) {\r
70             double s, t, xttt;\r
71             s = -c / b;\r
72             if ((s > 0.0) && (s < 1.0)) {\r
73                 t = 1.0 - s;\r
74                 xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111;\r
75                 bbox[0].extendTo(xttt);\r
76             }\r
77         }\r
78     } else {\r
79         /* s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a; */\r
80         D = b * b - 4 * a * c;\r
81         if (D >= 0.0) {\r
82             Geom::Coord d, s, t, xttt;\r
83             /* Have solution */\r
84             d = sqrt (D);\r
85             s = (-b + d) / (2 * a);\r
86             if ((s > 0.0) && (s < 1.0)) {\r
87                 t = 1.0 - s;\r
88                 xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111;\r
89                 bbox[0].extendTo(xttt);\r
90             }\r
91             s = (-b - d) / (2 * a);\r
92             if ((s > 0.0) && (s < 1.0)) {\r
93                 t = 1.0 - s;\r
94                 xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111;\r
95                 bbox[0].extendTo(xttt);\r
96             }\r
97         }\r
98     }\r
99 \r
100     a = 3 * y000 - 9 * y001 + 9 * y011 - 3 * y111;\r
101     b = 6 * y001 - 12 * y011 + 6 * y111;\r
102     c = 3 * y011 - 3 * y111;\r
103 \r
104     if (fabs (a) < Geom::EPSILON) {\r
105         /* s = -c / b */\r
106         if (fabs (b) > Geom::EPSILON) {\r
107             double s, t, yttt;\r
108             s = -c / b;\r
109             if ((s > 0.0) && (s < 1.0)) {\r
110                 t = 1.0 - s;\r
111                 yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111;\r
112                 bbox[1].extendTo(yttt);\r
113             }\r
114         }\r
115     } else {\r
116         /* s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a; */\r
117         D = b * b - 4 * a * c;\r
118         if (D >= 0.0) {\r
119             Geom::Coord d, s, t, yttt;\r
120             /* Have solution */\r
121             d = sqrt (D);\r
122             s = (-b + d) / (2 * a);\r
123             if ((s > 0.0) && (s < 1.0)) {\r
124                 t = 1.0 - s;\r
125                 yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111;\r
126                 bbox[1].extendTo(yttt);\r
127             }\r
128             s = (-b - d) / (2 * a);\r
129             if ((s > 0.0) && (s < 1.0)) {\r
130                 t = 1.0 - s;\r
131                 yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111;\r
132                 bbox[1].extendTo(yttt);\r
133             }\r
134         }\r
135     }\r
136 }\r
137 \r
138 Geom::Rect\r
139 bounds_fast_transformed(Geom::PathVector const & pv, Geom::Matrix const & t)\r
140 {\r
141     return bounds_exact_transformed(pv, t); //use this as it is faster for now! :)\r
142 //    return Geom::bounds_fast(pv * t);\r
143 }\r
144 \r
145 Geom::Rect\r
146 bounds_exact_transformed(Geom::PathVector const & pv, Geom::Matrix const & t)\r
147 {\r
148     Geom::Rect bbox;\r
149     \r
150     if (pv.empty())\r
151         return bbox;\r
152 \r
153     Geom::Point initial = pv.front().initialPoint() * t;\r
154     bbox = Geom::Rect(initial, initial);        // obtain well defined bbox as starting point to unionWith\r
155 \r
156     for (Geom::PathVector::const_iterator it = pv.begin(); it != pv.end(); ++it) {\r
157         bbox.expandTo(it->initialPoint() * t);\r
158 \r
159         // don't loop including closing segment, since that segment can never increase the bbox\r
160         for (Geom::Path::const_iterator cit = it->begin(); cit != it->end_open(); ++cit) {\r
161             Geom::Curve const *c = &*cit;\r
162 \r
163             if(Geom::LineSegment const *line_segment = dynamic_cast<Geom::LineSegment const *>(c))\r
164             {\r
165                 bbox.expandTo( (*line_segment)[1] * t );\r
166             }\r
167             else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const  *>(c))\r
168             {\r
169                 Geom::Point c0 = (*cubic_bezier)[0] * t;\r
170                 Geom::Point c1 = (*cubic_bezier)[1] * t;\r
171                 Geom::Point c2 = (*cubic_bezier)[2] * t;\r
172                 Geom::Point c3 = (*cubic_bezier)[3] * t;\r
173                 cubic_bbox( c0[0], c0[1],\r
174                             c1[0], c1[1],\r
175                             c2[0], c2[1],\r
176                             c3[0], c3[1],\r
177                             bbox );\r
178             }\r
179             else\r
180             {\r
181                 // should handle all not-so-easy curves:\r
182                 Geom::Curve *ctemp = cit->transformed(t);\r
183                 bbox.unionWith( ctemp->boundsExact());\r
184                 delete ctemp;\r
185             }\r
186         }\r
187     }\r
188     //return Geom::bounds_exact(pv * t);\r
189     return bbox;\r
190 }\r
191 \r
192 \r
193 \r
194 static void\r
195 geom_line_wind_distance (Geom::Coord x0, Geom::Coord y0, Geom::Coord x1, Geom::Coord y1, Geom::Point const &pt, int *wind, Geom::Coord *best)\r
196 {\r
197     Geom::Coord Ax, Ay, Bx, By, Dx, Dy, s;\r
198     Geom::Coord dist2;\r
199 \r
200     /* Find distance */\r
201     Ax = x0;\r
202     Ay = y0;\r
203     Bx = x1;\r
204     By = y1;\r
205     Dx = x1 - x0;\r
206     Dy = y1 - y0;\r
207     const Geom::Coord Px = pt[X];\r
208     const Geom::Coord Py = pt[Y];\r
209 \r
210     if (best) {\r
211         s = ((Px - Ax) * Dx + (Py - Ay) * Dy) / (Dx * Dx + Dy * Dy);\r
212         if (s <= 0.0) {\r
213             dist2 = (Px - Ax) * (Px - Ax) + (Py - Ay) * (Py - Ay);\r
214         } else if (s >= 1.0) {\r
215             dist2 = (Px - Bx) * (Px - Bx) + (Py - By) * (Py - By);\r
216         } else {\r
217             Geom::Coord Qx, Qy;\r
218             Qx = Ax + s * Dx;\r
219             Qy = Ay + s * Dy;\r
220             dist2 = (Px - Qx) * (Px - Qx) + (Py - Qy) * (Py - Qy);\r
221         }\r
222 \r
223         if (dist2 < (*best * *best)) *best = sqrt (dist2);\r
224     }\r
225 \r
226     if (wind) {\r
227         /* Find wind */\r
228         if ((Ax >= Px) && (Bx >= Px)) return;\r
229         if ((Ay >= Py) && (By >= Py)) return;\r
230         if ((Ay < Py) && (By < Py)) return;\r
231         if (Ay == By) return;\r
232         /* Ctach upper y bound */\r
233         if (Ay == Py) {\r
234             if (Ax < Px) *wind -= 1;\r
235             return;\r
236         } else if (By == Py) {\r
237             if (Bx < Px) *wind += 1;\r
238             return;\r
239         } else {\r
240             Geom::Coord Qx;\r
241             /* Have to calculate intersection */\r
242             Qx = Ax + Dx * (Py - Ay) / Dy;\r
243             if (Qx < Px) {\r
244                 *wind += (Dy > 0.0) ? 1 : -1;\r
245             }\r
246         }\r
247     }\r
248 }\r
249 \r
250 static void\r
251 geom_cubic_bbox_wind_distance (Geom::Coord x000, Geom::Coord y000,\r
252                  Geom::Coord x001, Geom::Coord y001,\r
253                  Geom::Coord x011, Geom::Coord y011,\r
254                  Geom::Coord x111, Geom::Coord y111,\r
255                  Geom::Point const &pt,\r
256                  Geom::Rect *bbox, int *wind, Geom::Coord *best,\r
257                  Geom::Coord tolerance)\r
258 {\r
259     Geom::Coord x0, y0, x1, y1, len2;\r
260     int needdist, needwind, needline;\r
261 \r
262     const Geom::Coord Px = pt[X];\r
263     const Geom::Coord Py = pt[Y];\r
264 \r
265     needdist = 0;\r
266     needwind = 0;\r
267     needline = 0;\r
268 \r
269     if (bbox) cubic_bbox (x000, y000, x001, y001, x011, y011, x111, y111, *bbox);\r
270 \r
271     x0 = MIN (x000, x001);\r
272     x0 = MIN (x0, x011);\r
273     x0 = MIN (x0, x111);\r
274     y0 = MIN (y000, y001);\r
275     y0 = MIN (y0, y011);\r
276     y0 = MIN (y0, y111);\r
277     x1 = MAX (x000, x001);\r
278     x1 = MAX (x1, x011);\r
279     x1 = MAX (x1, x111);\r
280     y1 = MAX (y000, y001);\r
281     y1 = MAX (y1, y011);\r
282     y1 = MAX (y1, y111);\r
283 \r
284     if (best) {\r
285         /* Quickly adjust to endpoints */\r
286         len2 = (x000 - Px) * (x000 - Px) + (y000 - Py) * (y000 - Py);\r
287         if (len2 < (*best * *best)) *best = (Geom::Coord) sqrt (len2);\r
288         len2 = (x111 - Px) * (x111 - Px) + (y111 - Py) * (y111 - Py);\r
289         if (len2 < (*best * *best)) *best = (Geom::Coord) sqrt (len2);\r
290 \r
291         if (((x0 - Px) < *best) && ((y0 - Py) < *best) && ((Px - x1) < *best) && ((Py - y1) < *best)) {\r
292             /* Point is inside sloppy bbox */\r
293             /* Now we have to decide, whether subdivide */\r
294             /* fixme: (Lauris) */\r
295             if (((y1 - y0) > 5.0) || ((x1 - x0) > 5.0)) {\r
296                 needdist = 1;\r
297             } else {\r
298                 needline = 1;\r
299             }\r
300         }\r
301     }\r
302     if (!needdist && wind) {\r
303         if ((y1 >= Py) && (y0 < Py) && (x0 < Px)) {\r
304             /* Possible intersection at the left */\r
305             /* Now we have to decide, whether subdivide */\r
306             /* fixme: (Lauris) */\r
307             if (((y1 - y0) > 5.0) || ((x1 - x0) > 5.0)) {\r
308                 needwind = 1;\r
309             } else {\r
310                 needline = 1;\r
311             }\r
312         }\r
313     }\r
314 \r
315     if (needdist || needwind) {\r
316         Geom::Coord x00t, x0tt, xttt, x1tt, x11t, x01t;\r
317         Geom::Coord y00t, y0tt, yttt, y1tt, y11t, y01t;\r
318         Geom::Coord s, t;\r
319 \r
320         t = 0.5;\r
321         s = 1 - t;\r
322 \r
323         x00t = s * x000 + t * x001;\r
324         x01t = s * x001 + t * x011;\r
325         x11t = s * x011 + t * x111;\r
326         x0tt = s * x00t + t * x01t;\r
327         x1tt = s * x01t + t * x11t;\r
328         xttt = s * x0tt + t * x1tt;\r
329 \r
330         y00t = s * y000 + t * y001;\r
331         y01t = s * y001 + t * y011;\r
332         y11t = s * y011 + t * y111;\r
333         y0tt = s * y00t + t * y01t;\r
334         y1tt = s * y01t + t * y11t;\r
335         yttt = s * y0tt + t * y1tt;\r
336 \r
337         geom_cubic_bbox_wind_distance (x000, y000, x00t, y00t, x0tt, y0tt, xttt, yttt, pt, NULL, wind, best, tolerance);\r
338         geom_cubic_bbox_wind_distance (xttt, yttt, x1tt, y1tt, x11t, y11t, x111, y111, pt, NULL, wind, best, tolerance);\r
339     } else if (1 || needline) {\r
340         geom_line_wind_distance (x000, y000, x111, y111, pt, wind, best);\r
341     }\r
342 }\r
343 \r
344 static void\r
345 geom_curve_bbox_wind_distance(Geom::Curve const * c, Geom::Matrix const &m,\r
346                  Geom::Point const &pt,\r
347                  Geom::Rect *bbox, int *wind, Geom::Coord *dist,\r
348                  Geom::Coord tolerance, Geom::Rect const *viewbox,\r
349                  Geom::Point &p0) // pass p0 through as it represents the last endpoint added (the finalPoint of last curve)\r
350 {\r
351     if(Geom::LineSegment const *line_segment = dynamic_cast<Geom::LineSegment const  *>(c)) {   // TODO: make it work for HLineSegment too! (use finalPoint)\r
352         Geom::Point pe = (*line_segment)[1] * m;\r
353         if (bbox) {\r
354             bbox->expandTo(pe);\r
355         }\r
356         if (dist || wind) {\r
357             if (wind) { // we need to pick fill, so do what we're told\r
358                 geom_line_wind_distance (p0[X], p0[Y], pe[X], pe[Y], pt, wind, dist);\r
359             } else { // only stroke is being picked; skip this segment if it's totally outside the viewbox\r
360                 Geom::Rect swept(p0, pe);\r
361                 if (!viewbox || swept.intersects(*viewbox))\r
362                     geom_line_wind_distance (p0[X], p0[Y], pe[X], pe[Y], pt, wind, dist);\r
363             }\r
364         }\r
365         p0 = pe;\r
366     }\r
367     else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const  *>(c)) {\r
368         Geom::Point p1 = (*cubic_bezier)[1] * m;\r
369         Geom::Point p2 = (*cubic_bezier)[2] * m;\r
370         Geom::Point p3 = (*cubic_bezier)[3] * m;\r
371 \r
372         // get approximate bbox from handles (convex hull property of beziers):\r
373         Geom::Rect swept(p0, p3);\r
374         swept.expandTo(p1);\r
375         swept.expandTo(p2);\r
376 \r
377         if (!viewbox || swept.intersects(*viewbox)) { // we see this segment, so do full processing\r
378             geom_cubic_bbox_wind_distance ( p0[X], p0[Y],\r
379                                             p1[X], p1[Y],\r
380                                             p2[X], p2[Y],\r
381                                             p3[X], p3[Y],\r
382                                             pt,\r
383                                             bbox, wind, dist, tolerance);\r
384         } else {\r
385             if (wind) { // if we need fill, we can just pretend it's a straight line\r
386                 geom_line_wind_distance (p0[X], p0[Y], p3[X], p3[Y], pt, wind, dist);\r
387             } else { // otherwise, skip it completely\r
388             }\r
389         }\r
390         p0 = p3;\r
391     } else { \r
392         //this case handles sbasis as well as all other curve types\r
393         Geom::Path sbasis_path = Geom::path_from_sbasis(c->toSBasis(), 0.1);\r
394 \r
395         //recurse to convert the new path resulting from the sbasis to svgd\r
396         for(Geom::Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {\r
397             geom_curve_bbox_wind_distance(&(*iter), m, pt, bbox, wind, dist, tolerance, viewbox, p0);\r
398         }\r
399     }\r
400 }\r
401 \r
402 /* Calculates...\r
403    and returns ... in *wind and the distance to ... in *dist.\r
404    Returns bounding box in *bbox if bbox!=NULL.\r
405  */\r
406 void\r
407 pathv_matrix_point_bbox_wind_distance (Geom::PathVector const & pathv, Geom::Matrix const &m, Geom::Point const &pt,\r
408                          Geom::Rect *bbox, int *wind, Geom::Coord *dist,\r
409                          Geom::Coord tolerance, Geom::Rect const *viewbox)\r
410 {\r
411     if (pathv.empty()) {\r
412         if (wind) *wind = 0;\r
413         if (dist) *dist = NR_HUGE;\r
414         return;\r
415     }\r
416 \r
417     // remember last point of last curve\r
418     Geom::Point p0(0,0);\r
419 \r
420     // remembering the start of subpath\r
421     Geom::Point p_start(0,0);\r
422     bool start_set = false;\r
423 \r
424     for (Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) {\r
425 \r
426         if (start_set) { // this is a new subpath\r
427             if (wind && (p0 != p_start)) // for correct fill picking, each subpath must be closed\r
428                 geom_line_wind_distance (p0[X], p0[Y], p_start[X], p_start[Y], pt, wind, dist);\r
429         }\r
430         p0 = it->initialPoint() * m;\r
431         p_start = p0;\r
432         start_set = true;\r
433         if (bbox) {\r
434             bbox->expandTo(p0);\r
435         }\r
436 \r
437         // loop including closing segment if path is closed\r
438         for (Geom::Path::const_iterator cit = it->begin(); cit != it->end_default(); ++cit) {\r
439             geom_curve_bbox_wind_distance(&*cit, m, pt, bbox, wind, dist, tolerance, viewbox, p0);\r
440         }\r
441     }\r
442 \r
443     if (start_set) { \r
444         if (wind && (p0 != p_start)) // for correct picking, each subpath must be closed\r
445             geom_line_wind_distance (p0[X], p0[Y], p_start[X], p_start[Y], pt, wind, dist);\r
446     }\r
447 }\r
448 \r
449 // temporary wrapper\r
450 void\r
451 pathv_matrix_point_bbox_wind_distance (Geom::PathVector const & pathv, NR::Matrix const &m, NR::Point const &pt,\r
452                          NR::Rect *bbox, int *wind, NR::Coord *dist,\r
453                          NR::Coord tolerance, NR::Rect const *viewbox)\r
454 {\r
455     Geom::Rect _bbox;\r
456     if (bbox)\r
457         _bbox = to_2geom(*bbox);\r
458     Geom::Coord _dist;\r
459     if (dist)\r
460         _dist = *dist;\r
461     Geom::Rect _viewbox;\r
462     if (viewbox)\r
463         _viewbox = to_2geom(*viewbox);\r
464 \r
465     pathv_matrix_point_bbox_wind_distance( pathv, to_2geom(m), to_2geom(pt),\r
466                                            bbox ? &_bbox : NULL,\r
467                                            wind,\r
468                                            dist ? &_dist : NULL,\r
469                                            tolerance,\r
470                                            viewbox ? &_viewbox : NULL );\r
471 \r
472     if (bbox)\r
473         *bbox = from_2geom(_bbox);\r
474     if (dist)\r
475         *dist = _dist;\r
476 }\r
477 //#################################################################################\r
478 \r
479 \r
480 \r
481 \r
482 /*\r
483   Local Variables:\r
484   mode:c++\r
485   c-file-style:"stroustrup"\r
486   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
487   indent-tabs-mode:nil\r
488   fill-column:99\r
489   End:\r
490 */\r
491 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r