1 /* 2 * SVG Elliptical Path Support Class 3 * 4 * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com> 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it either under the terms of the GNU Lesser General Public 8 * License version 2.1 as published by the Free Software Foundation 9 * (the "LGPL") or, at your option, under the terms of the Mozilla 10 * Public License Version 1.1 (the "MPL"). If you do not alter this 11 * notice, a recipient may use your version of this file under either 12 * the MPL or the LGPL. 13 * 14 * You should have received a copy of the LGPL along with this library 15 * in the file COPYING-LGPL-2.1; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 * You should have received a copy of the MPL along with this library 18 * in the file COPYING-MPL-1.1 19 * 20 * The contents of this file are subject to the Mozilla Public License 21 * Version 1.1 (the "License"); you may not use this file except in 22 * compliance with the License. You may obtain a copy of the License at 23 * http://www.mozilla.org/MPL/ 24 * 25 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY 26 * OF ANY KIND, either express or implied. See the LGPL or the MPL for 27 * the specific language governing rights and limitations. 28 */ 29 30 31 #ifndef _SVG_ELLIPTICAL_ARC_H_ 32 #define _SVG_ELLIPTICAL_ARC_H_ 33 34 35 #include "angle.h" 36 #include "matrix.h" 37 #include "sbasis.h" 38 #include "d2.h" 39 40 41 namespace Geom 42 { 43 44 class EllipticalArc 45 { 46 public: 47 EllipticalArc( Point _initial_point, Point _final_point, 48 double _rx, double _ry, 49 bool _large_arc, bool _sweep, 50 double _rot_angle = 0.0 51 ) m_initial_point(_initial_point)52 : m_initial_point(_initial_point), m_final_point(_final_point), 53 m_rx(_rx), m_ry(_ry), m_rot_angle(_rot_angle), 54 m_large_arc(_large_arc), m_sweep(_sweep) 55 { 56 assert( (ray(X) >= 0) && (ray(Y) >= 0) ); 57 if ( are_near(initialPoint(), finalPoint()) ) 58 { 59 m_start_angle = m_end_angle = 0; 60 m_center = initialPoint(); 61 } 62 else 63 { 64 calculate_center_and_extreme_angles(); 65 } 66 67 std::cerr << "start_angle: " << decimal_round(rad_to_deg(m_start_angle),2) << " ( " << m_start_angle << " )" << std::endl 68 << "end_angle: " << decimal_round(rad_to_deg(m_end_angle),2) << " ( " << m_end_angle << " )" << std::endl 69 << "center: " << m_center << std::endl; 70 } 71 72 public: center(Geom::Dim2 i)73 double center(Geom::Dim2 i) const 74 { 75 return m_center[i]; 76 } 77 center()78 Point center() const 79 { 80 return m_center; 81 } 82 initialPoint()83 Point initialPoint() const 84 { 85 return m_initial_point; 86 } 87 finalPoint()88 Point finalPoint() const 89 { 90 return m_final_point; 91 } 92 start_angle()93 double start_angle() const 94 { 95 return m_start_angle; 96 } 97 end_angle()98 double end_angle() const 99 { 100 return m_end_angle; 101 } 102 ray(Geom::Dim2 i)103 double ray(Geom::Dim2 i) const 104 { 105 return (i == 0) ? m_rx : m_ry; 106 } 107 large_arc_flag()108 bool large_arc_flag() const 109 { 110 return m_large_arc; 111 } 112 113 // void large_arc_flag(bool v) 114 // { 115 // m_large_arc = v; 116 // } 117 sweep_flag()118 bool sweep_flag() const 119 { 120 return m_sweep; 121 } 122 123 // void sweep_flag(bool v) 124 // { 125 // m_sweep = v; 126 // } 127 rotation_angle()128 double rotation_angle() const 129 { 130 return m_rot_angle; 131 } 132 setInitial(const Point _point)133 void setInitial( const Point _point) 134 { 135 m_initial_point = _point; 136 calculate_center_and_extreme_angles(); 137 } 138 setFinal(const Point _point)139 void setFinal( const Point _point) 140 { 141 m_final_point = _point; 142 calculate_center_and_extreme_angles(); 143 } 144 setExtremes(const Point & _initial_point,const Point & _final_point)145 void setExtremes( const Point& _initial_point, const Point& _final_point ) 146 { 147 m_initial_point = _initial_point; 148 m_final_point = _final_point; 149 calculate_center_and_extreme_angles(); 150 } 151 isDegenerate()152 bool isDegenerate() const 153 { 154 return are_near(initialPoint(), finalPoint()); 155 } 156 valueAt(Coord t,Dim2 d)157 double valueAt(Coord t, Dim2 d) const 158 { 159 Coord tt = from_01_to_02PI(t); 160 double sin_rot_angle = std::sin(rotation_angle()); 161 double cos_rot_angle = std::cos(rotation_angle()); 162 if ( d == X ) 163 { 164 return ray(X) * cos_rot_angle * std::cos(tt) 165 - ray(Y) * sin_rot_angle * std::sin(tt) 166 + center(X); 167 } 168 else 169 { 170 return ray(X) * sin_rot_angle * std::cos(tt) 171 + ray(Y) * cos_rot_angle * std::sin(tt) 172 + center(X); 173 } 174 } 175 pointAt(Coord t)176 Point pointAt(Coord t) const 177 { 178 Coord tt = from_01_to_02PI(t); 179 double sin_rot_angle = std::sin(rotation_angle()); 180 double cos_rot_angle = std::cos(rotation_angle()); 181 Matrix m( ray(X) * cos_rot_angle, ray(X) * sin_rot_angle, 182 -ray(Y) * sin_rot_angle, ray(Y) * cos_rot_angle, 183 center(X), center(Y) ); 184 Point p( std::cos(tt), std::sin(tt) ); 185 return p * m; 186 } 187 toSBasis()188 D2<SBasis> toSBasis() const 189 { 190 // the interval of parametrization has to be [0,1] 191 Coord et = start_angle() + ( sweep_flag() ? sweep_angle() : -sweep_angle() ); 192 Linear param(start_angle(), et); 193 // std::cerr << "param : " << param << std::endl; 194 Coord cos_rot_angle = std::cos(rotation_angle()); 195 Coord sin_rot_angle = std::sin(rotation_angle()); 196 // order = 4 seems to be enough to get perfect looking elliptical arc 197 // should it be choosen in function of the arc length anyway ? 198 // a user settable parameter: toSBasis(unsigned int order) ? 199 SBasis arc_x = ray(X) * cos(param,4); 200 SBasis arc_y = ray(Y) * sin(param,4); 201 D2<SBasis> arc; 202 arc[0] = arc_x * cos_rot_angle - arc_y * sin_rot_angle + Linear(center(X),center(X)); 203 arc[1] = arc_x * sin_rot_angle + arc_y * cos_rot_angle + Linear(center(Y),center(Y)); 204 return arc; 205 } 206 207 std::pair<EllipticalArc, EllipticalArc> subdivide(Coord t)208 subdivide(Coord t) const 209 { 210 EllipticalArc* arc1 = portion(0, t); 211 EllipticalArc* arc2 = portion(t, 1); 212 assert( arc1 != NULL && arc2 != NULL); 213 std::pair<EllipticalArc, EllipticalArc> arc_pair(*arc1, *arc2); 214 delete arc1; 215 delete arc2; 216 return arc_pair; 217 } 218 portion(double f,double t)219 EllipticalArc* portion(double f, double t) const 220 { 221 static const double M_2PI = 2*M_PI; 222 EllipticalArc* arc = new EllipticalArc( *this ); 223 arc->m_initial_point = pointAt(f); 224 arc->m_final_point = pointAt(t); 225 //std::cerr << "initial point: " << arc->m_initial_point << std::endl; 226 //std::cerr << "final point: " << arc->m_final_point << std::endl; 227 double sa = sweep_angle(); 228 //std::cerr << "sa: " << sa << std::endl; 229 arc->m_start_angle = m_start_angle + sa * f; 230 if ( arc->m_start_angle > M_2PI || are_near(arc->m_start_angle, M_2PI) ) 231 arc->m_start_angle -= M_2PI; 232 arc->m_end_angle = m_start_angle + sa * t; 233 if ( arc->m_end_angle > M_2PI || are_near(arc->m_end_angle, M_2PI) ) 234 arc->m_end_angle -= M_2PI; 235 //std::cerr << "start angle: " << arc->m_start_angle << std::endl; 236 //std::cerr << "end angle: " << arc->m_end_angle << std::endl; 237 //std::cerr << "sweep angle: " << arc->sweep_angle() << std::endl; 238 if (f > t) arc->m_sweep = !m_sweep; 239 if ( m_large_arc && (arc->sweep_angle() < M_PI) ) 240 arc->m_large_arc = false; 241 return arc; 242 } 243 244 // the arc is the same but traversed in the opposite direction reverse()245 EllipticalArc* reverse() const 246 { 247 EllipticalArc* rarc = new EllipticalArc( *this ); 248 rarc->m_sweep = !m_sweep; 249 rarc->m_initial_point = m_final_point; 250 rarc->m_final_point = m_initial_point; 251 rarc->m_start_angle = m_end_angle; 252 rarc->m_end_angle = m_start_angle; 253 return rarc; 254 } 255 256 private: 257 sweep_angle()258 double sweep_angle() const 259 { 260 Coord d = end_angle() - start_angle(); 261 if ( !sweep_flag() ) d = -d; 262 if ( d < 0 || are_near(d, 0) ) 263 d += 2*M_PI; 264 return d; 265 } 266 from_01_to_02PI(Coord t)267 Coord from_01_to_02PI(Coord t) const 268 { 269 if ( sweep_flag() ) 270 { 271 Coord angle = start_angle() + sweep_angle() * t; 272 if ( (angle > 2*M_PI) || are_near(angle, 2*M_PI) ) 273 angle -= 2*M_PI; 274 return angle; 275 } 276 else 277 { 278 Coord angle = start_angle() - sweep_angle() * t; 279 if ( angle < 0 ) angle += 2*M_PI; 280 return angle; 281 } 282 } 283 284 // NOTE: doesn't work with 360 deg arcs calculate_center_and_extreme_angles()285 void calculate_center_and_extreme_angles() 286 { 287 const double M_HALF_PI = M_PI/2; 288 const double M_2PI = 2*M_PI; 289 290 double sin_rot_angle = std::sin(rotation_angle()); 291 double cos_rot_angle = std::cos(rotation_angle()); 292 293 Point sp = sweep_flag() ? initialPoint() : finalPoint(); 294 Point ep = sweep_flag() ? finalPoint() : initialPoint(); 295 296 Matrix m( ray(X) * cos_rot_angle, ray(X) * sin_rot_angle, 297 -ray(Y) * sin_rot_angle, ray(Y) * cos_rot_angle, 298 0, 0 ); 299 Matrix im = m.inverse(); 300 Point sol = (ep - sp) * im; 301 std::cerr << "sol : " << sol << std::endl; 302 double half_sum_angle = std::atan2(-sol[X], sol[Y]); 303 double half_diff_angle; 304 if ( are_near(std::fabs(half_sum_angle), M_HALF_PI) ) 305 { 306 double anti_sgn_hsa = (half_sum_angle > 0) ? -1 : 1; 307 double arg = anti_sgn_hsa * sol[X] / 2; 308 // if |arg| is a little bit > 1 acos returns nan 309 if ( are_near(arg, 1) ) 310 half_diff_angle = 0; 311 else if ( are_near(arg, -1) ) 312 half_diff_angle = M_PI; 313 else 314 { 315 assert( -1 < arg && arg < 1 ); 316 // if it fails => there is no ellipse that satisfies the given constraints 317 half_diff_angle = std::acos( arg ); 318 } 319 320 half_diff_angle = M_HALF_PI - half_diff_angle; 321 } 322 else 323 { 324 double arg = sol[Y] / ( 2 * std::cos(half_sum_angle) ); 325 // if |arg| is a little bit > 1 asin returns nan 326 if ( are_near(arg, 1) ) 327 half_diff_angle = M_HALF_PI; 328 else if ( are_near(arg, -1) ) 329 half_diff_angle = -M_HALF_PI; 330 else 331 { 332 assert( -1 < arg && arg < 1 ); 333 // if it fails => there is no ellipse that satisfies the given constraints 334 half_diff_angle = std::asin( arg ); 335 } 336 } 337 std::cerr << "half_sum_angle : " << decimal_round(rad_to_deg(half_sum_angle),2) << " ( " << half_sum_angle << " )" << std::endl; 338 std::cerr << "half_diff_angle : " << decimal_round(rad_to_deg(half_diff_angle),2) << " ( " << half_diff_angle << " )" << std::endl; 339 //std::cerr << "cos(half_sum_angle) : " << std::cos(half_sum_angle) << std::endl; 340 //std::cerr << "sol[Y] / ( 2 * std::cos(half_sum_angle) ) : " << sol[Y] / ( 2 * std::cos(half_sum_angle) ) << std::endl; 341 342 if ( ( m_large_arc && half_diff_angle > 0 ) 343 || (!m_large_arc && half_diff_angle < 0 ) ) 344 { 345 half_diff_angle = -half_diff_angle; 346 } 347 if ( half_sum_angle < 0 ) half_sum_angle += M_2PI; 348 if ( half_diff_angle < 0 ) half_diff_angle += M_PI; 349 std::cerr << "half_sum_angle : " << decimal_round(rad_to_deg(half_sum_angle),2) << " ( " << half_sum_angle << " )" << std::endl; 350 std::cerr << "half_diff_angle : " << decimal_round(rad_to_deg(half_diff_angle),2) << " ( " << half_diff_angle << " )" << std::endl; 351 352 m_start_angle = half_sum_angle - half_diff_angle; 353 m_end_angle = half_sum_angle + half_diff_angle; 354 // 0 <= m_start_angle, m_end_angle < 2PI 355 if ( m_start_angle < 0 ) m_start_angle += M_2PI; 356 if ( m_end_angle > M_2PI || are_near(m_end_angle, M_2PI) ) m_end_angle -= M_2PI; 357 sol[0] = std::cos(m_start_angle); 358 sol[1] = std::sin(m_start_angle); 359 m_center = sp - sol * m; 360 if ( !sweep_flag() ) 361 { 362 double angle = m_start_angle; 363 m_start_angle = m_end_angle; 364 m_end_angle = angle; 365 } 366 } 367 368 private: 369 Point m_initial_point, m_final_point; 370 double m_rx, m_ry, m_rot_angle; 371 bool m_large_arc, m_sweep; 372 373 double m_start_angle, m_end_angle; 374 Point m_center; 375 }; 376 377 378 } 379 380 381 #endif /*_SVG_ELLIPTICAL_ARC_H_*/ 382 383 384 /* 385 Local Variables: 386 mode:c++ 387 c-file-style:"stroustrup" 388 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) 389 indent-tabs-mode:nil 390 fill-column:99 391 End: 392 */ 393 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : 394