Code

update to latest 2geom. this adds gsl dependency, doesn't seem to make inskape execut...
[inkscape.git] / src / 2geom / svg-elliptical-arc.cpp
1 /*\r
2  * SVG Elliptical Arc Class\r
3  *\r
4  * Copyright 2008  Marco Cecchetti <mrcekets at gmail.com>\r
5  *\r
6  * This library is free software; you can redistribute it and/or\r
7  * modify it either under the terms of the GNU Lesser General Public\r
8  * License version 2.1 as published by the Free Software Foundation\r
9  * (the "LGPL") or, at your option, under the terms of the Mozilla\r
10  * Public License Version 1.1 (the "MPL"). If you do not alter this\r
11  * notice, a recipient may use your version of this file under either\r
12  * the MPL or the LGPL.\r
13  *\r
14  * You should have received a copy of the LGPL along with this library\r
15  * in the file COPYING-LGPL-2.1; if not, write to the Free Software\r
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
17  * You should have received a copy of the MPL along with this library\r
18  * in the file COPYING-MPL-1.1\r
19  *\r
20  * The contents of this file are subject to the Mozilla Public License\r
21  * Version 1.1 (the "License"); you may not use this file except in\r
22  * compliance with the License. You may obtain a copy of the License at\r
23  * http://www.mozilla.org/MPL/\r
24  *\r
25  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY\r
26  * OF ANY KIND, either express or implied. See the LGPL or the MPL for\r
27  * the specific language governing rights and limitations.\r
28  */\r
29 \r
30 \r
31 #include "path.h"\r
32 #include "angle.h"\r
33 \r
34 #include <gsl/gsl_poly.h>\r
35 \r
36 #include <cfloat>\r
37 \r
38 \r
39 \r
40 \r
41 namespace Geom\r
42 {\r
43 \r
44 \r
45 Rect SVGEllipticalArc::boundsExact() const\r
46 {\r
47         std::vector<double> extremes(4);\r
48         double cosrot = std::cos(rotation_angle());\r
49         double sinrot = std::sin(rotation_angle());\r
50         extremes[0] = std::atan2( -ray(Y) * sinrot, ray(X) * cosrot );\r
51         extremes[1] = extremes[0] + M_PI;\r
52         if ( extremes[0] < 0 ) extremes[0] += 2*M_PI;   \r
53         extremes[2] = std::atan2( ray(Y) * cosrot, ray(X) * sinrot );\r
54         extremes[3] = extremes[2] + M_PI;\r
55         if ( extremes[2] < 0 ) extremes[2] += 2*M_PI;\r
56         \r
57         \r
58         std::vector<double>arc_extremes(4);\r
59         arc_extremes[0] = initialPoint()[X];\r
60         arc_extremes[1] = finalPoint()[X];\r
61         if ( arc_extremes[0] < arc_extremes[1] ) \r
62                 std::swap(arc_extremes[0], arc_extremes[1]);\r
63         arc_extremes[2] = initialPoint()[Y];\r
64         arc_extremes[3] = finalPoint()[Y];\r
65         if ( arc_extremes[2] < arc_extremes[3] ) \r
66                 std::swap(arc_extremes[2], arc_extremes[3]);\r
67         \r
68         \r
69         if ( start_angle() < end_angle() )\r
70         {\r
71                 if ( sweep_flag() )\r
72                 {\r
73                         for ( unsigned int i = 0; i < extremes.size(); ++i )\r
74                         {\r
75                                 if ( start_angle() < extremes[i] && extremes[i] < end_angle() )\r
76                                 {\r
77                                         arc_extremes[i] = pointAtAngle(extremes[i])[i >> 1];\r
78                                 }\r
79                         }\r
80                 }\r
81                 else\r
82                 {\r
83                         for ( unsigned int i = 0; i < extremes.size(); ++i )\r
84                         {\r
85                                 if ( start_angle() > extremes[i] || extremes[i] > end_angle() )\r
86                                 {\r
87                                         arc_extremes[i] = pointAtAngle(extremes[i])[i >> 1];\r
88                                 }\r
89                         }\r
90                 }\r
91         }\r
92         else\r
93         {\r
94                 if ( sweep_flag() )\r
95                 {\r
96                         for ( unsigned int i = 0; i < extremes.size(); ++i )\r
97                         {\r
98                                 if ( start_angle() < extremes[i] || extremes[i] < end_angle() )\r
99                                 {\r
100                                         arc_extremes[i] = pointAtAngle(extremes[i])[i >> 1];\r
101                                 }\r
102                         }\r
103                 }\r
104                 else\r
105                 {\r
106                         for ( unsigned int i = 0; i < extremes.size(); ++i )\r
107                         {\r
108                                 if ( start_angle() > extremes[i] && extremes[i] > end_angle() )\r
109                                 {\r
110                                         arc_extremes[i] = pointAtAngle(extremes[i])[i >> 1];\r
111                                 }\r
112                         }               \r
113                 }\r
114         }\r
115         \r
116         return Rect( Point(arc_extremes[1], arc_extremes[3]) , \r
117                              Point(arc_extremes[0], arc_extremes[2]) );\r
118 \r
119 }\r
120 \r
121 \r
122 std::vector<double> \r
123 SVGEllipticalArc::roots(double v, Dim2 d) const\r
124 {\r
125         if ( d > Y )\r
126         {\r
127                 THROW_RANGEERROR("dimention out of range");\r
128         }\r
129         \r
130         std::vector<double> sol;\r
131         if ( are_near(ray(X), 0) && are_near(ray(Y), 0) )\r
132         {\r
133                 if ( center(d) == v )\r
134                         sol.push_back(0);\r
135                 return sol;\r
136         }\r
137         \r
138         const char* msg[2][2] = \r
139         {\r
140                 { "d == X; ray(X) == 0; "\r
141                   "s = (v - center(X)) / ( -ray(Y) * std::sin(rotation_angle()) ); "\r
142                   "s should be contained in [-1,1]",\r
143                   "d == X; ray(Y) == 0; "\r
144                   "s = (v - center(X)) / ( ray(X) * std::cos(rotation_angle()) ); "\r
145                   "s should be contained in [-1,1]"\r
146                 },\r
147                 { "d == Y; ray(X) == 0; "\r
148                   "s = (v - center(X)) / ( ray(Y) * std::cos(rotation_angle()) ); "\r
149                   "s should be contained in [-1,1]",\r
150                   "d == Y; ray(Y) == 0; "\r
151                   "s = (v - center(X)) / ( ray(X) * std::sin(rotation_angle()) ); "\r
152                   "s should be contained in [-1,1]"\r
153                 },\r
154         };        \r
155         \r
156         for ( unsigned int dim = 0; dim < 2; ++dim )\r
157         {\r
158                 if ( are_near(ray(dim), 0) )\r
159                 {\r
160                         \r
161                         if ( initialPoint()[d] == v && finalPoint()[d] == v )\r
162                         {\r
163                                 THROW_EXCEPTION("infinite solutions");\r
164                         }\r
165                         if ( (initialPoint()[d] < finalPoint()[d])\r
166                                  && (initialPoint()[d] > v || finalPoint()[d] < v) )\r
167                         {\r
168                                 return sol;\r
169                         }\r
170                         if ( (initialPoint()[d] > finalPoint()[d])\r
171                                  && (finalPoint()[d] > v || initialPoint()[d] < v) )\r
172                         {\r
173                                 return sol;\r
174                         }\r
175                         double ray_prj;\r
176                         switch(d)\r
177                         {\r
178                                 case X:         \r
179                                         switch(dim)\r
180                                         {       \r
181                                                 case X: ray_prj = -ray(Y) * std::sin(rotation_angle());\r
182                                                                 break;\r
183                                                 case Y: ray_prj = ray(X) * std::cos(rotation_angle());\r
184                                                                 break;\r
185                                         }\r
186                                         break;\r
187                                 case Y:\r
188                                         switch(dim)\r
189                                         {       \r
190                                                 case X: ray_prj = ray(Y) * std::cos(rotation_angle());\r
191                                                                 break;\r
192                                                 case Y: ray_prj = ray(X) * std::sin(rotation_angle());\r
193                                                                 break;\r
194                                         }\r
195                                         break;\r
196                         }\r
197                         \r
198                         double s = (v - center(d)) / ray_prj;\r
199                         if ( s < -1 || s > 1 )\r
200                         {\r
201                                 THROW_LOGICALERROR(msg[d][dim]);\r
202                         }\r
203                         switch(dim)\r
204                         {       \r
205                                 case X: \r
206                                         s = std::asin(s); // return a value in [-PI/2,PI/2]\r
207                                         if ( logical_xor( sweep_flag(), are_near(start_angle(), M_PI/2) )  )\r
208                                         {\r
209                                                 if ( s < 0 ) s += 2*M_PI;\r
210                                         }\r
211                                         else\r
212                                         {\r
213                                                 s = M_PI - s;\r
214                                                 if (!(s < 2*M_PI) ) s -= 2*M_PI;\r
215                                         }\r
216                                         break;\r
217                                 case Y: \r
218                                         s = std::acos(s); // return a value in [0,PI]\r
219                                         if ( logical_xor( sweep_flag(), are_near(start_angle(), 0) ) )\r
220                                         {\r
221                                                 s = 2*M_PI - s;\r
222                                                 if ( !(s < 2*M_PI) ) s -= 2*M_PI;\r
223                                         }\r
224                                         break;\r
225                         }\r
226                         \r
227                         //std::cerr << "s = " << rad_to_deg(s);\r
228                         s = map_to_01(s);\r
229                         //std::cerr << " -> t: " << s << std::endl;\r
230                         if ( !(s < 0 || s > 1) )\r
231                                 sol.push_back(s);\r
232                         return sol;\r
233                 }\r
234         }\r
235                 \r
236         double rotx, roty;\r
237         switch(d)\r
238         {\r
239                 case X: \r
240                         rotx = std::cos(rotation_angle());\r
241                         roty = -std::sin(rotation_angle());\r
242                         break;\r
243                 case Y:\r
244                         rotx = std::sin(rotation_angle());\r
245                         roty = std::cos(rotation_angle());\r
246                         break;\r
247         }\r
248         double rxrotx = ray(X) * rotx;\r
249         double c_v = center(d) - v;\r
250 \r
251         double a = -rxrotx + c_v;\r
252         double b = ray(Y) * roty;\r
253         double c = rxrotx + c_v;\r
254         //std::cerr << "a = " << a << std::endl;\r
255         //std::cerr << "b = " << b << std::endl;\r
256         //std::cerr << "c = " << c << std::endl;\r
257         \r
258         if ( are_near(a,0) )\r
259         {\r
260                 sol.push_back(M_PI);\r
261                 if ( !are_near(b,0) )\r
262                 {\r
263                         double s = 2 * std::atan(-c/(2*b));\r
264                         if ( s < 0 ) s += 2*M_PI;\r
265                         sol.push_back(s);\r
266                 }\r
267         }\r
268         else\r
269         {\r
270                 double delta = b * b - a * c;\r
271                 //std::cerr << "delta = " << delta << std::endl;\r
272                 if ( are_near(delta, 0) )\r
273                 {\r
274                         double s = 2 * std::atan(-b/a);\r
275                         if ( s < 0 ) s += 2*M_PI;\r
276                         sol.push_back(s);\r
277                 }\r
278                 else if ( delta > 0 )\r
279                 {               \r
280                         double sq = std::sqrt(delta);\r
281                         double s = 2 * std::atan( (-b - sq) / a );\r
282                         if ( s < 0 ) s += 2*M_PI;\r
283                         sol.push_back(s);\r
284                         s = 2 * std::atan( (-b + sq) / a );\r
285                         if ( s < 0 ) s += 2*M_PI;\r
286                         sol.push_back(s);\r
287                 }\r
288         }\r
289         \r
290         std::vector<double> arc_sol;\r
291         for (unsigned int i = 0; i < sol.size(); ++i )\r
292         {\r
293                 //std::cerr << "s = " << rad_to_deg(sol[i]);\r
294                 sol[i] = map_to_01(sol[i]);\r
295                 //std::cerr << " -> t: " << sol[i] << std::endl;\r
296                 if ( !(sol[i] < 0 || sol[i] > 1) )\r
297                         arc_sol.push_back(sol[i]);\r
298         }\r
299         return arc_sol;\r
300         \r
301         \r
302 //      return SBasisCurve(toSBasis()).roots(v, d);\r
303 }\r
304 \r
305 // D(E(t,C),t) = E(t+PI/2,O)\r
306 Curve* SVGEllipticalArc::derivative() const\r
307 {\r
308         SVGEllipticalArc* result = new SVGEllipticalArc(*this);\r
309         result->m_center[X] = result->m_center[Y] = 0;\r
310         result->m_start_angle += M_PI/2;\r
311         if( !( result->m_start_angle < 2*M_PI ) )\r
312         {\r
313                 result->m_start_angle -= 2*M_PI;\r
314         }\r
315         result->m_end_angle += M_PI/2;\r
316         if( !( result->m_end_angle < 2*M_PI ) )\r
317         {\r
318                 result->m_end_angle -= 2*M_PI;\r
319         }\r
320         result->m_initial_point = result->pointAtAngle( result->start_angle() );\r
321         result->m_final_point = result->pointAtAngle( result->end_angle() );\r
322         return result;\r
323         \r
324 }\r
325 \r
326 std::vector<Point> \r
327 SVGEllipticalArc::pointAndDerivatives(Coord t, unsigned int n) const\r
328 {\r
329         std::vector<Point> result;\r
330         result.reserve(n);\r
331         double angle = map_unit_interval_on_circular_arc(t, start_angle(), \r
332                                                                  end_angle(), sweep_flag());\r
333         SVGEllipticalArc ea(*this);\r
334         ea.m_center = Point(0,0);\r
335         unsigned int m = std::min(n, 4u);\r
336         for ( unsigned int i = 0; i < m; ++i )\r
337         {\r
338                 result.push_back( ea.pointAtAngle(angle) );\r
339                 angle += M_PI/2;\r
340                 if ( !(angle < 2*M_PI) ) angle -= 2*M_PI;\r
341         }\r
342         m = n / 4;\r
343         for ( unsigned int i = 1; i < m; ++i )\r
344         {\r
345                 for ( unsigned int j = 0; j < 4; ++j )\r
346                         result.push_back( result[j] );\r
347         }\r
348         m = n - 4 * m;\r
349         for ( unsigned int i = 0; i < m; ++i )\r
350         {\r
351                 result.push_back( result[i] );\r
352         }\r
353         if ( !result.empty() ) // n != 0\r
354                 result[0] = pointAtAngle(angle);\r
355         return result;\r
356 }\r
357 \r
358 D2<SBasis> SVGEllipticalArc::toSBasis() const\r
359 {\r
360     // the interval of parametrization has to be [0,1]\r
361     Coord et = start_angle() + ( sweep_flag() ? sweep_angle() : -sweep_angle() );\r
362     Linear param(start_angle(), et);\r
363     Coord cos_rot_angle = std::cos(rotation_angle());\r
364     Coord sin_rot_angle = std::sin(rotation_angle());\r
365     // order = 4 seems to be enough to get a perfect looking elliptical arc\r
366     // should it be choosen in function of the arc length anyway ?\r
367     // or maybe a user settable parameter: toSBasis(unsigned int order) ?\r
368     SBasis arc_x = ray(X) * cos(param,4);\r
369     SBasis arc_y = ray(Y) * sin(param,4);\r
370     D2<SBasis> arc;\r
371     arc[0] = arc_x * cos_rot_angle - arc_y * sin_rot_angle + Linear(center(X),center(X));\r
372     arc[1] = arc_x * sin_rot_angle + arc_y * cos_rot_angle + Linear(center(Y),center(Y));\r
373     return arc;\r
374 }\r
375 \r
376 \r
377 bool SVGEllipticalArc::containsAngle(Coord angle) const\r
378 {\r
379         if ( sweep_flag() )\r
380                 if ( start_angle() < end_angle() )\r
381                         if ( !( angle < start_angle() || angle > end_angle() ) )\r
382                                 return true;\r
383                         else\r
384                                 return false;\r
385                 else\r
386                         if ( !( angle < start_angle() && angle > end_angle() ) )\r
387                                 return true;\r
388                         else\r
389                                 return false;\r
390         else\r
391                 if ( start_angle() > end_angle() )\r
392                         if ( !( angle > start_angle() || angle < end_angle() ) )\r
393                                 return true;\r
394                         else\r
395                                 return false;\r
396                 else\r
397                         if ( !( angle > start_angle() && angle < end_angle() ) )\r
398                                 return true;\r
399                         else\r
400                                 return false;\r
401 }\r
402 \r
403 \r
404 double SVGEllipticalArc::valueAtAngle(Coord t, Dim2 d) const\r
405 {\r
406     double sin_rot_angle = std::sin(rotation_angle());\r
407     double cos_rot_angle = std::cos(rotation_angle());\r
408     if ( d == X )\r
409     {\r
410         return    ray(X) * cos_rot_angle * std::cos(t) \r
411                 - ray(Y) * sin_rot_angle * std::sin(t) \r
412                 + center(X);\r
413     }\r
414     else if ( d == Y )\r
415     {\r
416         return    ray(X) * sin_rot_angle * std::cos(t) \r
417                 + ray(Y) * cos_rot_angle * std::sin(t) \r
418                 + center(Y);\r
419     }\r
420     THROW_RANGEERROR("dimension parameter out of range");\r
421 }\r
422 \r
423 \r
424 Curve* SVGEllipticalArc::portion(double f, double t) const \r
425 {\r
426         if (f < 0) f = 0;\r
427         if (f > 1) f = 1;\r
428         if (t < 0) t = 0;\r
429         if (t > 1) t = 1;\r
430         if ( are_near(f, t) )\r
431         {\r
432                 SVGEllipticalArc* arc = new SVGEllipticalArc();\r
433                 arc->m_center = arc->m_initial_point = arc->m_final_point = pointAt(f);\r
434                 arc->m_start_angle = arc->m_end_angle = m_start_angle;\r
435                 arc->m_rot_angle = m_rot_angle;\r
436                 arc->m_sweep = m_sweep;\r
437                 arc->m_large_arc = m_large_arc;\r
438         }\r
439     SVGEllipticalArc* arc = new SVGEllipticalArc( *this );\r
440     arc->m_initial_point = pointAt(f);\r
441     arc->m_final_point = pointAt(t);\r
442     double sa = sweep_flag() ? sweep_angle() : -sweep_angle();\r
443     arc->m_start_angle = m_start_angle + sa * f;\r
444     if ( !(arc->m_start_angle < 2*M_PI) )\r
445         arc->m_start_angle -= 2*M_PI;\r
446     if ( arc->m_start_angle < 0 )\r
447         arc->m_start_angle += 2*M_PI;\r
448     arc->m_end_angle = m_start_angle + sa * t;\r
449     if ( !(arc->m_end_angle < 2*M_PI) )\r
450         arc->m_end_angle -= 2*M_PI;\r
451     if ( arc->m_end_angle < 0 )\r
452         arc->m_end_angle += 2*M_PI;\r
453     if ( f > t ) arc->m_sweep = !sweep_flag();\r
454     if ( large_arc_flag() && (arc->sweep_angle() < M_PI) )\r
455         arc->m_large_arc = false;\r
456     return arc;\r
457 }\r
458 \r
459 // NOTE: doesn't work with 360 deg arcs\r
460 void SVGEllipticalArc::calculate_center_and_extreme_angles()\r
461 {\r
462     if ( are_near(initialPoint(), finalPoint()) )\r
463     {\r
464         if ( are_near(ray(X), 0) && are_near(ray(Y), 0) )\r
465         {\r
466                 m_start_angle = m_end_angle = 0;\r
467                 m_center = initialPoint();\r
468                 return;\r
469         }\r
470         else\r
471         {\r
472                 THROW_RANGEERROR("initial and final point are the same");\r
473         }\r
474     }\r
475         if ( are_near(ray(X), 0) && are_near(ray(Y), 0) )\r
476         { // but initialPoint != finalPoint\r
477                 THROW_RANGEERROR(\r
478                         "there is no ellipse that satisfies the given constraints: "\r
479                         "ray(X) == 0 && ray(Y) == 0 but initialPoint != finalPoint"\r
480                 );\r
481         }\r
482         if ( are_near(ray(Y), 0) )\r
483         {\r
484                 Point v = initialPoint() - finalPoint();\r
485                 if ( are_near(L2sq(v), 4*ray(X)*ray(X)) )\r
486                 {\r
487                         double angle = std::atan2(v[Y], v[X]);\r
488                         if (angle < 0) angle += 2*M_PI;\r
489                         if ( are_near( angle, rotation_angle() ) )\r
490                         {\r
491                                 m_start_angle = 0;\r
492                                 m_end_angle = M_PI;\r
493                                 m_center = v/2 + finalPoint();\r
494                                 return;\r
495                         }\r
496                         angle -= M_PI;\r
497                         if ( angle < 0 ) angle += 2*M_PI;\r
498                         if ( are_near( angle, rotation_angle() ) )\r
499                         {\r
500                                 m_start_angle = M_PI;\r
501                                 m_end_angle = 0;\r
502                                 m_center = v/2 + finalPoint();\r
503                                 return;\r
504                         }\r
505                         THROW_RANGEERROR(\r
506                                 "there is no ellipse that satisfies the given constraints: "\r
507                                 "ray(Y) == 0 "\r
508                                 "and slope(initialPoint - finalPoint) != rotation_angle "\r
509                                 "and != rotation_angle + PI"\r
510                         );\r
511                 }\r
512                 if ( L2sq(v) > 4*ray(X)*ray(X) )\r
513                 {\r
514                         THROW_RANGEERROR(\r
515                                 "there is no ellipse that satisfies the given constraints: "\r
516                                 "ray(Y) == 0 and distance(initialPoint, finalPoint) > 2*ray(X)"\r
517                         );\r
518                 }\r
519                 else\r
520                 {\r
521                         THROW_RANGEERROR(\r
522                                 "there is infinite ellipses that satisfy the given constraints: "\r
523                                 "ray(Y) == 0  and distance(initialPoint, finalPoint) < 2*ray(X)"\r
524                         );\r
525                 }\r
526                 \r
527         }\r
528         \r
529         if ( are_near(ray(X), 0) )\r
530         {\r
531                 Point v = initialPoint() - finalPoint();\r
532                 if ( are_near(L2sq(v), 4*ray(Y)*ray(Y)) )\r
533                 {\r
534                         double angle = std::atan2(v[Y], v[X]);\r
535                         if (angle < 0) angle += 2*M_PI;\r
536                         double rot_angle = rotation_angle() + M_PI/2;\r
537                         if ( !(rot_angle < 2*M_PI) ) rot_angle -= 2*M_PI;\r
538                         if ( are_near( angle, rot_angle ) )\r
539                         {\r
540                                 m_start_angle = M_PI/2;\r
541                                 m_end_angle = 3*M_PI/2;\r
542                                 m_center = v/2 + finalPoint();\r
543                                 return;\r
544                         }\r
545                         angle -= M_PI;\r
546                         if ( angle < 0 ) angle += 2*M_PI;\r
547                         if ( are_near( angle, rot_angle ) )\r
548                         {\r
549                                 m_start_angle = 3*M_PI/2;\r
550                                 m_end_angle = M_PI/2;\r
551                                 m_center = v/2 + finalPoint();\r
552                                 return;\r
553                         }\r
554                         THROW_RANGEERROR(\r
555                                 "there is no ellipse that satisfies the given constraints: "\r
556                                 "ray(X) == 0 "\r
557                                 "and slope(initialPoint - finalPoint) != rotation_angle + PI/2 "\r
558                                 "and != rotation_angle + (3/2)*PI"\r
559                         );\r
560                 }\r
561                 if ( L2sq(v) > 4*ray(Y)*ray(Y) )\r
562                 {\r
563                         THROW_RANGEERROR(\r
564                                 "there is no ellipse that satisfies the given constraints: "\r
565                                 "ray(X) == 0 and distance(initialPoint, finalPoint) > 2*ray(Y)"\r
566                         );\r
567                 }\r
568                 else\r
569                 {\r
570                         THROW_RANGEERROR(\r
571                                 "there is infinite ellipses that satisfy the given constraints: "\r
572                                 "ray(X) == 0  and distance(initialPoint, finalPoint) < 2*ray(Y)"\r
573                         );\r
574                 }\r
575                 \r
576         }\r
577         \r
578     double sin_rot_angle = std::sin(rotation_angle());\r
579     double cos_rot_angle = std::cos(rotation_angle());\r
580 \r
581     Point sp = sweep_flag() ? initialPoint() : finalPoint();\r
582     Point ep = sweep_flag() ? finalPoint() : initialPoint();\r
583 \r
584     Matrix m( ray(X) * cos_rot_angle, ray(X) * sin_rot_angle,\r
585              -ray(Y) * sin_rot_angle, ray(Y) * cos_rot_angle,\r
586               0,                      0 );\r
587     Matrix im = m.inverse();\r
588     Point sol = (ep - sp) * im;\r
589     double half_sum_angle = std::atan2(-sol[X], sol[Y]);\r
590     double half_diff_angle;\r
591     if ( are_near(std::fabs(half_sum_angle), M_PI/2) )\r
592     {\r
593         double anti_sgn_hsa = (half_sum_angle > 0) ? -1 : 1;\r
594         double arg = anti_sgn_hsa * sol[X] / 2;\r
595         // if |arg| is a little bit > 1 acos returns nan\r
596         if ( are_near(arg, 1) )\r
597             half_diff_angle = 0;\r
598         else if ( are_near(arg, -1) )\r
599             half_diff_angle = M_PI;\r
600         else\r
601         {\r
602                 if ( !(-1 < arg && arg < 1) )\r
603                         THROW_RANGEERROR(\r
604                                 "there is no ellipse that satisfies the given constraints"\r
605                         );\r
606             // assert( -1 < arg && arg < 1 );\r
607             // if it fails \r
608             // => there is no ellipse that satisfies the given constraints\r
609             half_diff_angle = std::acos( arg );\r
610         }\r
611 \r
612         half_diff_angle = M_PI/2 - half_diff_angle;\r
613     }\r
614     else\r
615     {\r
616         double  arg = sol[Y] / ( 2 * std::cos(half_sum_angle) );\r
617         // if |arg| is a little bit > 1 asin returns nan\r
618         if ( are_near(arg, 1) ) \r
619             half_diff_angle = M_PI/2;\r
620         else if ( are_near(arg, -1) )\r
621             half_diff_angle = -M_PI/2;\r
622         else\r
623         {\r
624                 if ( !(-1 < arg && arg < 1) )\r
625                         THROW_RANGEERROR(\r
626                                 "there is no ellipse that satisfies the given constraints"\r
627                         );\r
628             // assert( -1 < arg && arg < 1 );\r
629             // if it fails \r
630             // => there is no ellipse that satisfies the given constraints\r
631             half_diff_angle = std::asin( arg );\r
632         }\r
633     }\r
634 \r
635     if (   ( m_large_arc && half_diff_angle > 0 ) \r
636         || (!m_large_arc && half_diff_angle < 0 ) )\r
637     {\r
638         half_diff_angle = -half_diff_angle;\r
639     }\r
640     if ( half_sum_angle < 0 ) half_sum_angle += 2*M_PI;\r
641     if ( half_diff_angle < 0 ) half_diff_angle += M_PI;\r
642     \r
643     m_start_angle = half_sum_angle - half_diff_angle;\r
644     m_end_angle =  half_sum_angle + half_diff_angle;\r
645     // 0 <= m_start_angle, m_end_angle < 2PI\r
646     if ( m_start_angle < 0 ) m_start_angle += 2*M_PI;\r
647     if( !(m_end_angle < 2*M_PI) ) m_end_angle -= 2*M_PI;\r
648     sol[0] = std::cos(m_start_angle);\r
649     sol[1] = std::sin(m_start_angle);\r
650     m_center = sp - sol * m;\r
651     if ( !sweep_flag() )\r
652     {\r
653         double angle = m_start_angle;\r
654         m_start_angle = m_end_angle;\r
655         m_end_angle = angle;\r
656     }\r
657 }\r
658 \r
659 Coord SVGEllipticalArc::map_to_02PI(Coord t) const\r
660 {\r
661     if ( sweep_flag() )\r
662     {\r
663         Coord angle = start_angle() + sweep_angle() * t;\r
664         if ( !(angle < 2*M_PI) )\r
665             angle -= 2*M_PI;\r
666         return angle;\r
667     }\r
668     else\r
669     {\r
670         Coord angle = start_angle() - sweep_angle() * t;\r
671         if ( angle < 0 ) angle += 2*M_PI;\r
672         return angle;\r
673     }\r
674 }\r
675 \r
676 Coord SVGEllipticalArc::map_to_01(Coord angle) const \r
677 {\r
678         return map_circular_arc_on_unit_interval(angle, start_angle(), \r
679                                                          end_angle(), sweep_flag());\r
680 }\r
681 \r
682 \r
683 std::vector<double> SVGEllipticalArc::\r
684 allNearestPoints( Point const& p, double from, double to ) const\r
685 {\r
686         if ( from > to ) std::swap(from, to);\r
687         if ( from < 0 || to > 1 )\r
688         {\r
689                 THROW_RANGEERROR("[from,to] interval out of range");\r
690         }\r
691         std::vector<double> result;\r
692         if ( ( are_near(ray(X), 0) && are_near(ray(Y), 0) )  || are_near(from, to) )\r
693         {\r
694                 result.push_back(from);\r
695                 return result;\r
696         }\r
697         else if ( are_near(ray(X), 0) || are_near(ray(Y), 0) )\r
698         {\r
699                 LineSegment seg(pointAt(from), pointAt(to));\r
700                 Point np = seg.pointAt( seg.nearestPoint(p) );\r
701                 if ( are_near(ray(Y), 0) )\r
702                 {\r
703                         if ( are_near(rotation_angle(), M_PI/2) \r
704                                  || are_near(rotation_angle(), 3*M_PI/2) )\r
705                         {\r
706                                 result = roots(np[Y], Y);\r
707                         }\r
708                         else\r
709                         {\r
710                                 result = roots(np[X], X);\r
711                         }\r
712                 }\r
713                 else\r
714                 {\r
715                         if ( are_near(rotation_angle(), M_PI/2) \r
716                                  || are_near(rotation_angle(), 3*M_PI/2) )\r
717                         {\r
718                                 result = roots(np[X], X);\r
719                         }\r
720                         else\r
721                         {\r
722                                 result = roots(np[Y], Y);\r
723                         }\r
724                 }\r
725                 return result;\r
726         }\r
727         else if ( are_near(ray(X), ray(Y)) )\r
728         {\r
729                 Point r = p - center();\r
730                 if ( are_near(r, Point(0,0)) )\r
731                 {\r
732                         THROW_EXCEPTION("infinite nearest points");\r
733                 }\r
734                 // TODO: implement case r != 0\r
735 //              Point np = ray(X) * unit_vector(r);\r
736 //              std::vector<double> solX = roots(np[X],X);\r
737 //              std::vector<double> solY = roots(np[Y],Y);\r
738 //              double t;\r
739 //              if ( are_near(solX[0], solY[0]) || are_near(solX[0], solY[1]))\r
740 //              {\r
741 //                      t = solX[0];\r
742 //              }\r
743 //              else\r
744 //              {\r
745 //                      t = solX[1];\r
746 //              }\r
747 //              if ( !(t < from || t > to) )\r
748 //              {\r
749 //                      result.push_back(t);\r
750 //              }\r
751 //              else\r
752 //              {\r
753 //                      \r
754 //              }\r
755         }\r
756         \r
757         // solve the equation <D(E(t),t)|E(t)-p> == 0\r
758         // that provides min and max distance points \r
759         // on the ellipse E wrt the point p\r
760         // after the substitutions: \r
761         // cos(t) = (1 - s^2) / (1 + s^2)\r
762         // sin(t) = 2t / (1 + s^2)\r
763         // where s = tan(t/2)\r
764         // we get a 4th degree equation in s\r
765         /*\r
766          *      ry s^4 ((-cy + py) Cos[Phi] + (cx - px) Sin[Phi]) + \r
767          *      ry ((cy - py) Cos[Phi] + (-cx + px) Sin[Phi]) + \r
768          *      2 s^3 (rx^2 - ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi]) + \r
769          *      2 s (-rx^2 + ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi])\r
770          */\r
771 \r
772         Point p_c = p - center();\r
773         double rx2_ry2 = (ray(X) - ray(Y)) * (ray(X) + ray(Y));\r
774         double cosrot = std::cos( rotation_angle() );\r
775         double sinrot = std::sin( rotation_angle() );\r
776         double expr1 = ray(X) * (p_c[X] * cosrot + p_c[Y] * sinrot);\r
777         double coeff[5];\r
778         coeff[4] = ray(Y) * ( p_c[Y] * cosrot - p_c[X] * sinrot );\r
779         coeff[3] = 2 * ( rx2_ry2 + expr1 );\r
780         coeff[2] = 0;\r
781         coeff[1] = 2 * ( -rx2_ry2 + expr1 );\r
782         coeff[0] = -coeff[4];\r
783         \r
784 //      for ( unsigned int i = 0; i < 5; ++i )\r
785 //              std::cerr << "c[" << i << "] = " << coeff[i] << std::endl;\r
786         \r
787         std::vector<double> real_sol;\r
788         // gsl_poly_complex_solve raises an error \r
789         // if the leading coefficient is zero\r
790         if ( are_near(coeff[4], 0) )  \r
791         {\r
792                 real_sol.push_back(0);\r
793                 if ( !are_near(coeff[3], 0) )\r
794                 {\r
795                         double sq = -coeff[1] / coeff[3];\r
796                         if ( sq > 0 )\r
797                         {\r
798                                 double s = std::sqrt(sq);\r
799                                 real_sol.push_back(s);\r
800                                 real_sol.push_back(-s);\r
801                         }\r
802                 }\r
803         }\r
804         else\r
805         {\r
806                 double sol[8];\r
807                 gsl_poly_complex_workspace * w = gsl_poly_complex_workspace_alloc(5);\r
808                 gsl_poly_complex_solve(coeff, 5, w, sol );\r
809                 gsl_poly_complex_workspace_free(w);\r
810                 \r
811                 for ( unsigned int i = 0; i < 4; ++i )\r
812                 {\r
813                         if ( sol[2*i+1] == 0 ) real_sol.push_back(sol[2*i]);\r
814                 }\r
815         }\r
816                 \r
817         for ( unsigned int i = 0; i < real_sol.size(); ++i )\r
818         {\r
819                 real_sol[i] = 2 * std::atan(real_sol[i]);\r
820                 if ( real_sol[i] < 0 ) real_sol[i] += 2*M_PI;\r
821         }\r
822         // when s -> Infinity then <D(E)|E-p> -> 0 iff coeff[4] == 0\r
823         // so we add M_PI to the solutions being lim arctan(s) = PI when s->Infinity\r
824         if ( (real_sol.size() % 2) != 0 )\r
825         {\r
826                 real_sol.push_back(M_PI);\r
827         }\r
828         \r
829         double mindistsq1 = std::numeric_limits<double>::max();\r
830         double mindistsq2 = std::numeric_limits<double>::max();\r
831         double dsq;\r
832         unsigned int mi1, mi2;\r
833         for ( unsigned int i = 0; i < real_sol.size(); ++i )\r
834         {\r
835                 dsq = distanceSq(p, pointAtAngle(real_sol[i]));\r
836                 if ( mindistsq1 > dsq )\r
837                 {\r
838                         mindistsq2 = mindistsq1;\r
839                         mi2 = mi1;\r
840                         mindistsq1 = dsq;\r
841                         mi1 = i;\r
842                 }\r
843                 else if ( mindistsq2 > dsq )\r
844                 {\r
845                         mindistsq2 = dsq;\r
846                         mi2 = i;\r
847                 }\r
848         }\r
849         \r
850         double t = map_to_01( real_sol[mi1] );\r
851         if ( !(t < from || t > to) )\r
852         {\r
853                 result.push_back(t);\r
854         }\r
855         \r
856         bool second_sol = false; \r
857         t = map_to_01( real_sol[mi2] );\r
858         if ( real_sol.size() == 4 && !(t < from || t > to) )\r
859         {\r
860         if ( result.empty() || are_near(mindistsq1, mindistsq2) )\r
861         {\r
862                 result.push_back(t);\r
863                 second_sol = true;\r
864         }\r
865         }\r
866         \r
867         // we need to test extreme points too\r
868         double dsq1 = distanceSq(p, pointAt(from));\r
869         double dsq2 = distanceSq(p, pointAt(to));\r
870         if ( second_sol )\r
871         {\r
872                 if ( mindistsq2 > dsq1 )\r
873                 {\r
874                         result.clear();\r
875                         result.push_back(from);\r
876                         mindistsq2 = dsq1;\r
877                 }\r
878                 else if ( are_near(mindistsq2, dsq) )\r
879                 {\r
880                         result.push_back(from);\r
881                 }\r
882                 if ( mindistsq2 > dsq2 )\r
883                 {\r
884                         result.clear();\r
885                         result.push_back(to);\r
886                 }\r
887                 else if ( are_near(mindistsq2, dsq2) )\r
888                 {\r
889                         result.push_back(to);\r
890                 }\r
891                 \r
892         }\r
893         else\r
894         {\r
895                 if ( result.empty() )\r
896                 {\r
897                         if ( are_near(dsq1, dsq2) )\r
898                         {\r
899                                 result.push_back(from);\r
900                                 result.push_back(to);\r
901                         }\r
902                         else if ( dsq2 > dsq1 )\r
903                         {\r
904                                 result.push_back(from);\r
905                         }\r
906                         else\r
907                         {\r
908                                 result.push_back(to);\r
909                         }\r
910                 }\r
911         }\r
912         \r
913         return result;\r
914 }\r
915 \r
916 \r
917 } // end namespace Geom\r
918 \r
919 \r
920 /*\r
921   Local Variables:\r
922   mode:c++\r
923   c-file-style:"stroustrup"\r
924   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
925   indent-tabs-mode:nil\r
926   fill-column:99\r
927   End:\r
928 */\r
929 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r
930 \r
931 \r