GCC Code Coverage Report


Directory: ./
File: include/2geom/angle.h
Date: 2024-03-18 17:01:34
Exec Total Coverage
Lines: 105 107 98.1%
Functions: 32 32 100.0%
Branches: 75 124 60.5%

Line Branch Exec Source
1 /**
2 * \file
3 * \brief Various trigoniometric helper functions
4 *//*
5 * Authors:
6 * Johan Engelen <goejendaagh@zonnet.nl>
7 * Marco Cecchetti <mrcekets at gmail.com>
8 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
9 *
10 * Copyright (C) 2007-2010 Authors
11 *
12 * This library is free software; you can redistribute it and/or
13 * modify it either under the terms of the GNU Lesser General Public
14 * License version 2.1 as published by the Free Software Foundation
15 * (the "LGPL") or, at your option, under the terms of the Mozilla
16 * Public License Version 1.1 (the "MPL"). If you do not alter this
17 * notice, a recipient may use your version of this file under either
18 * the MPL or the LGPL.
19 *
20 * You should have received a copy of the LGPL along with this library
21 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 * You should have received a copy of the MPL along with this library
24 * in the file COPYING-MPL-1.1
25 *
26 * The contents of this file are subject to the Mozilla Public License
27 * Version 1.1 (the "License"); you may not use this file except in
28 * compliance with the License. You may obtain a copy of the License at
29 * http://www.mozilla.org/MPL/
30 *
31 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
32 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
33 * the specific language governing rights and limitations.
34 *
35 */
36
37 #ifndef LIB2GEOM_SEEN_ANGLE_H
38 #define LIB2GEOM_SEEN_ANGLE_H
39
40 #include <cmath>
41 #include <boost/operators.hpp>
42 #include <2geom/exception.h>
43 #include <2geom/coord.h>
44 #include <2geom/point.h>
45
46 namespace Geom {
47
48 #ifndef M_PI
49 # define M_PI 3.14159265358979323846
50 #endif
51 #ifndef M_1_2PI
52 # define M_1_2PI 0.159154943091895335768883763373
53 #endif
54
55 /** @brief Wrapper for angular values.
56 *
57 * This class is a convenience wrapper that implements the behavior generally expected of angles,
58 * like addition modulo \f$2\pi\f$. The value returned from the default conversion
59 * to <tt>double</tt> is in the range \f$[-\pi, \pi)\f$ - the convention used by C's
60 * math library.
61 *
62 * This class holds only a single floating point value, so passing it by value will generally
63 * be faster than passing it by const reference.
64 *
65 * @ingroup Primitives
66 */
67 class Angle
68 : boost::additive< Angle
69 , boost::additive< Angle, Coord
70 , boost::equality_comparable< Angle
71 , boost::equality_comparable< Angle, Coord
72 > > > >
73 {
74 public:
75 6 Angle() : _angle(0) {}
76 10299 Angle(Coord v) : _angle(v) { _normalize(); } // this can be called implicitly
77 84885 explicit Angle(Point const &p) : _angle(atan2(p)) { _normalize(); }
78 Angle(Point const &a, Point const &b) : _angle(angle_between(a, b)) { _normalize(); }
79 110393 operator Coord() const { return radians(); }
80 115532 Angle &operator+=(Angle o) {
81 115532 _angle += o._angle;
82 115532 _normalize();
83 115532 return *this;
84 }
85 112002 Angle &operator-=(Angle o) {
86 112002 _angle -= o._angle;
87 112002 _normalize();
88 112002 return *this;
89 }
90 115532 Angle &operator+=(Coord a) {
91
2/4
✓ Branch 2 taken 115532 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 115532 times.
✗ Branch 6 not taken.
115532 *this += Angle(a);
92 115532 return *this;
93 }
94 10652 Angle &operator-=(Coord a) {
95
2/4
✓ Branch 2 taken 10652 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10652 times.
✗ Branch 6 not taken.
10652 *this -= Angle(a);
96 10652 return *this;
97 }
98 53564 bool operator==(Angle o) const {
99 53564 return _angle == o._angle;
100 }
101 bool operator==(Coord c) const {
102 return _angle == Angle(c)._angle;
103 }
104
105 /** @brief Get the angle as radians.
106 * @return Number in range \f$[-\pi, \pi)\f$. */
107 110439 Coord radians() const {
108
2/2
✓ Branch 0 taken 9914 times.
✓ Branch 1 taken 100525 times.
110439 return _angle >= M_PI ? _angle - 2*M_PI : _angle;
109 }
110 /** @brief Get the angle as positive radians.
111 * @return Number in range \f$[0, 2\pi)\f$. */
112 972340 Coord radians0() const {
113 972340 return _angle;
114 }
115 /** @brief Get the angle as degrees in math convention.
116 * @return Number in range [-180, 180) obtained by scaling the result of radians()
117 * by \f$180/\pi\f$. */
118 Coord degrees() const { return radians() * (180.0 / M_PI); }
119 /** @brief Get the angle as degrees in clock convention.
120 * This method converts the angle to the "clock convention": angles start from the +Y axis
121 * and grow clockwise. This means that 0 corresponds to \f$\pi/2\f$ radians,
122 * 90 to 0 radians, 180 to \f$-\pi/2\f$ radians, and 270 to \f$\pi\f$ radians.
123 * @return A number in the range [0, 360).
124 */
125 Coord degreesClock() const {
126 Coord ret = 90.0 - _angle * (180.0 / M_PI);
127 if (ret < 0) ret += 360;
128 return ret;
129 }
130 /** @brief Create an angle from its measure in radians. */
131 static Angle from_radians(Coord d) {
132 Angle a(d);
133 return a;
134 }
135 /** @brief Create an angle from its measure in degrees. */
136 static Angle from_degrees(Coord d) {
137 Angle a(d * (M_PI / 180.0));
138 return a;
139 }
140 /** @brief Create an angle from its measure in degrees in clock convention.
141 * @see Angle::degreesClock() */
142 static Angle from_degrees_clock(Coord d) {
143 // first make sure d is in [0, 360)
144 d = std::fmod(d, 360.0);
145 if (d < 0) d += 360.0;
146 Coord rad = M_PI/2 - d * (M_PI / 180.0);
147 if (rad < 0) rad += 2*M_PI;
148 Angle a;
149 a._angle = rad;
150 return a;
151 }
152 private:
153
154 10299 void _normalize() {
155 10299 _angle = std::fmod(_angle, 2*M_PI);
156
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 10241 times.
10299 if (_angle < 0) _angle += 2*M_PI;
157 //_angle -= floor(_angle * (1.0/(2*M_PI))) * 2*M_PI;
158 10299 }
159 Coord _angle; // this is always in [0, 2pi)
160 friend class AngleInterval;
161 };
162
163 76 inline Angle distance(Angle const &a, Angle const &b) {
164 // the distance cannot be larger than M_PI.
165 76 Coord ac = a.radians0();
166 76 Coord bc = b.radians0();
167 76 Coord d = fabs(ac - bc);
168
3/4
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 67 times.
✓ Branch 4 taken 76 times.
✗ Branch 5 not taken.
76 return Angle(d > M_PI ? 2*M_PI - d : d);
169 }
170
171 /** @brief Directed angular interval.
172 *
173 * Wrapper for directed angles with defined start and end values. Useful e.g. for representing
174 * the portion of an ellipse in an elliptical arc. Both extreme angles are contained
175 * in the interval (it is a closed interval). Angular intervals can also be interptered
176 * as functions \f$f: [0, 1] \to [-\pi, \pi)\f$, which return the start angle for 0,
177 * the end angle for 1, and interpolate linearly for other values. Note that such functions
178 * are not continuous if the interval crosses the angle \f$\pi\f$.
179 *
180 * This class can represent all directed angular intervals, including empty ones.
181 * However, not all possible intervals can be created with the constructors.
182 * For full control, use the setInitial(), setFinal() and setAngles() methods.
183 *
184 * @ingroup Primitives
185 */
186 class AngleInterval
187 : boost::equality_comparable< AngleInterval >
188 {
189 public:
190 2 AngleInterval() {}
191 /** @brief Create an angular interval from two angles and direction.
192 * If the initial and final angle are the same, a degenerate interval
193 * (containing only one angle) will be created.
194 * @param s Starting angle
195 * @param e Ending angle
196 * @param cw Which direction the interval goes. True means that it goes
197 * in the direction of increasing angles, while false means in the direction
198 * of decreasing angles. */
199 19726 AngleInterval(Angle s, Angle e, bool cw = false)
200 19726 : _start_angle(s), _end_angle(e), _sweep(cw), _full(false)
201 19726 {}
202 7 AngleInterval(double s, double e, bool cw = false)
203 7 : _start_angle(s), _end_angle(e), _sweep(cw), _full(false)
204 7 {}
205 /** @brief Create an angular interval from three angles.
206 * If the inner angle is exactly equal to initial or final angle,
207 * the sweep flag will be set to true, i.e. the interval will go
208 * in the direction of increasing angles.
209 *
210 * If the initial and final angle are the same, but the inner angle
211 * is different, a full angle in the direction of increasing angles
212 * will be created.
213 *
214 * @param s Initial angle
215 * @param inner Angle contained in the interval
216 * @param e Final angle */
217 AngleInterval(Angle s, Angle inner, Angle e)
218 : _start_angle(s)
219 , _end_angle(e)
220 , _sweep((inner-s).radians0() <= (e-s).radians0())
221 , _full(s == e && s != inner)
222 {
223 if (_full) {
224 _sweep = true;
225 }
226 }
227
228 /// Get the start angle.
229 9943 Angle initialAngle() const { return _start_angle; }
230 /// Get the end angle.
231 9897 Angle finalAngle() const { return _end_angle; }
232 /// Check whether the interval goes in the direction of increasing angles.
233 30122 bool sweep() const { return _sweep; }
234 /// Check whether the interval contains only a single angle.
235 bool isDegenerate() const {
236 return _start_angle == _end_angle && !_full;
237 }
238 /// Check whether the interval contains all angles.
239 53531 bool isFull() const {
240
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 53531 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
53531 return _start_angle == _end_angle && _full;
241 }
242
243 /** @brief Set the initial angle.
244 * @param a Angle to set
245 * @param prefer_full Whether to set a full angular interval when
246 * the initial angle is set to the final angle */
247 11592 void setInitial(Angle a, bool prefer_full = false) {
248 11592 _start_angle = a;
249
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 11592 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
11592 _full = prefer_full && a == _end_angle;
250 11592 }
251
252 /** @brief Set the final angle.
253 * @param a Angle to set
254 * @param prefer_full Whether to set a full angular interval when
255 * the initial angle is set to the final angle */
256 11592 void setFinal(Angle a, bool prefer_full = false) {
257 11592 _end_angle = a;
258
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 11592 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
11592 _full = prefer_full && a == _start_angle;
259 11592 }
260 /** @brief Set both angles at once.
261 * The direction (sweep flag) is left unchanged.
262 * @param s Initial angle
263 * @param e Final angle
264 * @param prefer_full Whether to set a full interval when the passed
265 * initial and final angle are the same */
266 48 void setAngles(Angle s, Angle e, bool prefer_full = false) {
267 48 _start_angle = s;
268 48 _end_angle = e;
269
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
48 _full = prefer_full && s == e;
270 48 }
271 /// Set whether the interval goes in the direction of increasing angles.
272 7 void setSweep(bool s) { _sweep = s; }
273
274 /// Reverse the direction of the interval while keeping contained values the same.
275 40016 void reverse() {
276 using std::swap;
277 40016 swap(_start_angle, _end_angle);
278 40016 _sweep = !_sweep;
279 40016 }
280 /// Get a new interval with reversed direction.
281 AngleInterval reversed() const {
282 AngleInterval result(*this);
283 result.reverse();
284 return result;
285 }
286
287 /// Get an angle corresponding to the specified time value.
288 20615 Angle angleAt(Coord t) const {
289
1/2
✓ Branch 1 taken 20615 times.
✗ Branch 2 not taken.
20615 Coord span = extent();
290
3/4
✓ Branch 2 taken 10466 times.
✓ Branch 3 taken 10149 times.
✓ Branch 5 taken 20615 times.
✗ Branch 6 not taken.
20615 Angle ret = _start_angle.radians0() + span * (_sweep ? t : -t);
291 20615 return ret;
292 }
293 Angle operator()(Coord t) const { return angleAt(t); }
294
295 /** @brief Compute a time value that would evaluate to the given angle.
296 * If the start and end angle are exactly the same, NaN will be returned.
297 * Negative values will be returned for angles between the initial angle
298 * and the angle exactly opposite the midpoint of the interval. */
299 20178 Coord timeAtAngle(Angle a) const {
300
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20178 times.
20178 if (_full) {
301 Angle ta = _sweep ? a - _start_angle : _start_angle - a;
302 return ta.radians0() / (2*M_PI);
303 }
304 20178 Coord ex = extent();
305 20178 Coord outex = 2*M_PI - ex;
306
2/2
✓ Branch 0 taken 10043 times.
✓ Branch 1 taken 10135 times.
20178 if (_sweep) {
307
1/2
✓ Branch 3 taken 10043 times.
✗ Branch 4 not taken.
10043 Angle midout = _start_angle - outex / 2;
308
2/4
✓ Branch 2 taken 10043 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 10043 times.
✗ Branch 7 not taken.
10043 Angle acmp = a - midout, scmp = _start_angle - midout;
309
2/2
✓ Branch 2 taken 6160 times.
✓ Branch 3 taken 3883 times.
10043 if (acmp.radians0() >= scmp.radians0()) {
310
1/2
✓ Branch 2 taken 6160 times.
✗ Branch 3 not taken.
6160 return (a - _start_angle).radians0() / ex;
311 } else {
312
1/2
✓ Branch 2 taken 3883 times.
✗ Branch 3 not taken.
3883 return -(_start_angle - a).radians0() / ex;
313 }
314 } else {
315
1/2
✓ Branch 3 taken 10135 times.
✗ Branch 4 not taken.
10135 Angle midout = _start_angle + outex / 2;
316
2/4
✓ Branch 2 taken 10135 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 10135 times.
✗ Branch 7 not taken.
10135 Angle acmp = a - midout, scmp = _start_angle - midout;
317
2/2
✓ Branch 2 taken 8961 times.
✓ Branch 3 taken 1174 times.
10135 if (acmp.radians0() <= scmp.radians0()) {
318
1/2
✓ Branch 2 taken 8961 times.
✗ Branch 3 not taken.
8961 return (_start_angle - a).radians0() / ex;
319 } else {
320
1/2
✓ Branch 2 taken 1174 times.
✗ Branch 3 not taken.
1174 return -(a - _start_angle).radians0() / ex;
321 }
322 }
323 }
324
325 /// Check whether the interval includes the given angle.
326 209258 bool contains(Angle a) const {
327
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 209258 times.
209258 if (_full) return true;
328 209258 Coord s = _start_angle.radians0();
329 209258 Coord e = _end_angle.radians0();
330 209258 Coord x = a.radians0();
331
2/2
✓ Branch 0 taken 202762 times.
✓ Branch 1 taken 6496 times.
209258 if (_sweep) {
332
6/6
✓ Branch 0 taken 154994 times.
✓ Branch 1 taken 47768 times.
✓ Branch 2 taken 89990 times.
✓ Branch 3 taken 65004 times.
✓ Branch 4 taken 72479 times.
✓ Branch 5 taken 17511 times.
202762 if (s < e) return x >= s && x <= e;
333
4/4
✓ Branch 0 taken 46486 times.
✓ Branch 1 taken 1282 times.
✓ Branch 2 taken 23041 times.
✓ Branch 3 taken 23445 times.
47768 return x >= s || x <= e;
334 } else {
335
6/6
✓ Branch 0 taken 6034 times.
✓ Branch 1 taken 462 times.
✓ Branch 2 taken 2877 times.
✓ Branch 3 taken 3157 times.
✓ Branch 4 taken 946 times.
✓ Branch 5 taken 1931 times.
6496 if (s > e) return x <= s && x >= e;
336
4/4
✓ Branch 0 taken 363 times.
✓ Branch 1 taken 99 times.
✓ Branch 2 taken 46 times.
✓ Branch 3 taken 317 times.
462 return x <= s || x >= e;
337 }
338 }
339 /** @brief Extent of the angle interval.
340 * Equivalent to the absolute value of the sweep angle.
341 * @return Extent in range \f$[0, 2\pi)\f$. */
342 40816 Coord extent() const {
343
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40816 times.
40816 if (_full) return 2*M_PI;
344 40816 return _sweep
345
5/8
✓ Branch 0 taken 20519 times.
✓ Branch 1 taken 20297 times.
✓ Branch 4 taken 20519 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 20297 times.
✓ Branch 9 taken 20519 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
102151 ? (_end_angle - _start_angle).radians0()
346
3/6
✓ Branch 1 taken 20297 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20297 times.
✓ Branch 5 taken 20519 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
81632 : (_start_angle - _end_angle).radians0();
347 }
348 /** @brief Get the sweep angle of the interval.
349 * This is the value you need to add to the initial angle to get the final angle.
350 * It is positive when sweep is true. Denoted as \f$\Delta\theta\f$ in the SVG
351 * elliptical arc implementation notes. */
352 23 Coord sweepAngle() const {
353
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
23 if (_full) return _sweep ? 2*M_PI : -2*M_PI;
354 23 Coord sa = _end_angle.radians0() - _start_angle.radians0();
355
3/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
23 if (_sweep && sa < 0) sa += 2*M_PI;
356
3/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 11 times.
23 if (!_sweep && sa > 0) sa -= 2*M_PI;
357 23 return sa;
358 }
359
360 /// Check another interval for equality.
361 bool operator==(AngleInterval const &other) const {
362 if (_start_angle != other._start_angle) return false;
363 if (_end_angle != other._end_angle) return false;
364 if (_sweep != other._sweep) return false;
365 if (_full != other._full) return false;
366 return true;
367 }
368
369 static AngleInterval create_full(Angle start, bool sweep = true) {
370 AngleInterval result;
371 result._start_angle = result._end_angle = start;
372 result._sweep = sweep;
373 result._full = true;
374 return result;
375 }
376
377 private:
378 Angle _start_angle;
379 Angle _end_angle;
380 bool _sweep;
381 bool _full;
382 };
383
384 /** @brief Given an angle in degrees, return radians
385 * @relates Angle */
386 188 inline constexpr Coord rad_from_deg(Coord deg) { return deg / 180.0 * M_PI;}
387 /** @brief Given an angle in radians, return degrees
388 * @relates Angle */
389 12 inline constexpr Coord deg_from_rad(Coord rad) { return rad / M_PI * 180.0;}
390
391 } // namespace Geom
392
393 namespace std {
394 template <> class iterator_traits<Geom::Angle> {};
395 }
396
397 #endif // LIB2GEOM_SEEN_ANGLE_H
398
399 /*
400 Local Variables:
401 mode:c++
402 c-file-style:"stroustrup"
403 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
404 indent-tabs-mode:nil
405 fill-column:99
406 End:
407 */
408 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
409