GCC Code Coverage Report


Directory: ./
File: src/2geom/affine.cpp
Date: 2024-03-18 17:01:34
Exec Total Coverage
Lines: 130 189 68.8%
Functions: 31 42 73.8%
Branches: 174 238 73.1%

Line Branch Exec Source
1 /*
2 * Authors:
3 * Lauris Kaplinski <lauris@kaplinski.com>
4 * Michael G. Sloan <mgsloan@gmail.com>
5 *
6 * This code is in public domain
7 */
8
9 #include <2geom/affine.h>
10 #include <2geom/point.h>
11 #include <2geom/polynomial.h>
12 #include <2geom/utils.h>
13
14 namespace Geom {
15
16 /** Creates a Affine given an axis and origin point.
17 * The axis is represented as two vectors, which represent skew, rotation, and scaling in two dimensions.
18 * from_basis(Point(1, 0), Point(0, 1), Point(0, 0)) would return the identity matrix.
19
20 \param x_basis the vector for the x-axis.
21 \param y_basis the vector for the y-axis.
22 \param offset the translation applied by the matrix.
23 \return The new Affine.
24 */
25 //NOTE: Inkscape's version is broken, so when including this version, you'll have to search for code with this func
26 Affine from_basis(Point const &x_basis, Point const &y_basis, Point const &offset) {
27 return Affine(x_basis[X], x_basis[Y],
28 y_basis[X], y_basis[Y],
29 offset [X], offset [Y]);
30 }
31
32 3 Point Affine::xAxis() const {
33 3 return Point(_c[0], _c[1]);
34 }
35
36 3 Point Affine::yAxis() const {
37 3 return Point(_c[2], _c[3]);
38 }
39
40 /// Gets the translation imparted by the Affine.
41 Point Affine::translation() const {
42 return Point(_c[4], _c[5]);
43 }
44
45 void Affine::setXAxis(Point const &vec) {
46 for(int i = 0; i < 2; i++)
47 _c[i] = vec[i];
48 }
49
50 void Affine::setYAxis(Point const &vec) {
51 for(int i = 0; i < 2; i++)
52 _c[i + 2] = vec[i];
53 }
54
55 /// Sets the translation imparted by the Affine.
56 365430 void Affine::setTranslation(Point const &loc) {
57
2/2
✓ Branch 0 taken 730860 times.
✓ Branch 1 taken 365430 times.
1096290 for(int i = 0; i < 2; i++)
58 730860 _c[i + 4] = loc[i];
59 365430 }
60
61 /** Calculates the amount of x-scaling imparted by the Affine. This is the scaling applied to
62 * the original x-axis region. It is \emph{not} the overall x-scaling of the transformation.
63 * Equivalent to L2(m.xAxis()). */
64 double Affine::expansionX() const {
65 return sqrt(_c[0] * _c[0] + _c[1] * _c[1]);
66 }
67
68 /** Calculates the amount of y-scaling imparted by the Affine. This is the scaling applied before
69 * the other transformations. It is \emph{not} the overall y-scaling of the transformation.
70 * Equivalent to L2(m.yAxis()). */
71 double Affine::expansionY() const {
72 return sqrt(_c[2] * _c[2] + _c[3] * _c[3]);
73 }
74
75 void Affine::setExpansionX(double val) {
76 double exp_x = expansionX();
77 if (exp_x != 0.0) { //TODO: best way to deal with it is to skip op?
78 double coef = val / expansionX();
79 for (unsigned i = 0; i < 2; ++i) {
80 _c[i] *= coef;
81 }
82 }
83 }
84
85 void Affine::setExpansionY(double val) {
86 double exp_y = expansionY();
87 if (exp_y != 0.0) { //TODO: best way to deal with it is to skip op?
88 double coef = val / expansionY();
89 for (unsigned i = 2; i < 4; ++i) {
90 _c[i] *= coef;
91 }
92 }
93 }
94
95 /** Sets this matrix to be the Identity Affine. */
96 1 void Affine::setIdentity() {
97 1 _c[0] = 1.0; _c[1] = 0.0;
98 1 _c[2] = 0.0; _c[3] = 1.0;
99 1 _c[4] = 0.0; _c[5] = 0.0;
100 1 }
101
102 /** @brief Check whether this matrix is an identity matrix.
103 * @param eps Numerical tolerance
104 * @return True iff the matrix is of the form
105 * \f$\left[\begin{array}{ccc}
106 1 & 0 & 0 \\
107 0 & 1 & 0 \\
108 0 & 0 & 1 \end{array}\right]\f$ */
109 14 bool Affine::isIdentity(Coord eps) const {
110
4/4
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
21 return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
111
3/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
7 are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) &&
112
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
20 are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
113 }
114
115 /** @brief Check whether this matrix represents a pure translation.
116 * Will return true for the identity matrix, which represents a zero translation.
117 * @param eps Numerical tolerance
118 * @return True iff the matrix is of the form
119 * \f$\left[\begin{array}{ccc}
120 1 & 0 & 0 \\
121 0 & 1 & 0 \\
122 a & b & 1 \end{array}\right]\f$ */
123 14 bool Affine::isTranslation(Coord eps) const {
124
4/4
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
21 return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
125
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
21 are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps);
126 }
127 /** @brief Check whether this matrix represents a pure nonzero translation.
128 * @param eps Numerical tolerance
129 * @return True iff the matrix is of the form
130 * \f$\left[\begin{array}{ccc}
131 1 & 0 & 0 \\
132 0 & 1 & 0 \\
133 a & b & 1 \end{array}\right]\f$ and \f$a, b \neq 0\f$ */
134 14 bool Affine::isNonzeroTranslation(Coord eps) const {
135
4/4
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
21 return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
136
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
23 are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) &&
137
3/4
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
16 (!are_near(_c[4], 0.0, eps) || !are_near(_c[5], 0.0, eps));
138 }
139
140 /** @brief Check whether this matrix represents pure scaling.
141 * @param eps Numerical tolerance
142 * @return True iff the matrix is of the form
143 * \f$\left[\begin{array}{ccc}
144 a & 0 & 0 \\
145 0 & b & 0 \\
146 0 & 0 & 1 \end{array}\right]\f$. */
147 1217 bool Affine::isScale(Coord eps) const {
148
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1215 times.
1217 if (isSingular(eps)) return false;
149
4/4
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 20 times.
✓ Branch 5 taken 2 times.
1260 return are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) &&
150
3/4
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 1192 times.
✓ Branch 4 taken 20 times.
✗ Branch 5 not taken.
1260 are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
151 }
152
153 /** @brief Check whether this matrix represents pure, nonzero scaling.
154 * @param eps Numerical tolerance
155 * @return True iff the matrix is of the form
156 * \f$\left[\begin{array}{ccc}
157 a & 0 & 0 \\
158 0 & b & 0 \\
159 0 & 0 & 1 \end{array}\right]\f$ and \f$a, b \neq 1\f$. */
160 14 bool Affine::isNonzeroScale(Coord eps) const {
161
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 12 times.
14 if (isSingular(eps)) return false;
162
3/4
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 2 times.
24 return (!are_near(_c[0], 1.0, eps) || !are_near(_c[3], 1.0, eps)) && //NOTE: these are the diags, and the next line opposite diags
163
3/4
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 1 times.
20 are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) &&
164
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
30 are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
165 }
166
167 /** @brief Check whether this matrix represents pure uniform scaling.
168 * @param eps Numerical tolerance
169 * @return True iff the matrix is of the form
170 * \f$\left[\begin{array}{ccc}
171 a_1 & 0 & 0 \\
172 0 & a_2 & 0 \\
173 0 & 0 & 1 \end{array}\right]\f$ where \f$|a_1| = |a_2|\f$. */
174 15 bool Affine::isUniformScale(Coord eps) const {
175
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 13 times.
15 if (isSingular(eps)) return false;
176
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 3 times.
24 return are_near(fabs(_c[0]), fabs(_c[3]), eps) &&
177
4/4
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 2 times.
26 are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) &&
178
3/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
31 are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
179 }
180
181 /** @brief Check whether this matrix represents pure, nonzero uniform scaling.
182 * @param eps Numerical tolerance
183 * @return True iff the matrix is of the form
184 * \f$\left[\begin{array}{ccc}
185 a_1 & 0 & 0 \\
186 0 & a_2 & 0 \\
187 0 & 0 & 1 \end{array}\right]\f$ where \f$|a_1| = |a_2|\f$
188 * and \f$a_1, a_2 \neq 1\f$. */
189 15 bool Affine::isNonzeroUniformScale(Coord eps) const {
190
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 13 times.
15 if (isSingular(eps)) return false;
191 // we need to test both c0 and c3 to handle the case of flips,
192 // which should be treated as nonzero uniform scales
193
3/4
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 2 times.
26 return !(are_near(_c[0], 1.0, eps) && are_near(_c[3], 1.0, eps)) &&
194
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 2 times.
16 are_near(fabs(_c[0]), fabs(_c[3]), eps) &&
195
3/4
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 1 times.
17 are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) &&
196
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 9 times.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
31 are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
197 }
198
199 /** @brief Check whether this matrix represents a pure rotation.
200 * @param eps Numerical tolerance
201 * @return True iff the matrix is of the form
202 * \f$\left[\begin{array}{ccc}
203 a & b & 0 \\
204 -b & a & 0 \\
205 0 & 0 & 1 \end{array}\right]\f$ and \f$a^2 + b^2 = 1\f$. */
206 14 bool Affine::isRotation(Coord eps) const {
207
4/4
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 3 times.
30 return are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) &&
208
5/6
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 2 times.
34 are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps) &&
209 18 are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps);
210 }
211
212 /** @brief Check whether this matrix represents a pure, nonzero rotation.
213 * @param eps Numerical tolerance
214 * @return True iff the matrix is of the form
215 * \f$\left[\begin{array}{ccc}
216 a & b & 0 \\
217 -b & a & 0 \\
218 0 & 0 & 1 \end{array}\right]\f$, \f$a^2 + b^2 = 1\f$ and \f$a \neq 1\f$. */
219 14 bool Affine::isNonzeroRotation(Coord eps) const {
220
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
24 return !are_near(_c[0], 1.0, eps) &&
221
3/4
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 2 times.
20 are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) &&
222
5/6
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 2 times.
32 are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps) &&
223 17 are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps);
224 }
225
226 /** @brief Check whether this matrix represents a non-zero rotation about any point.
227 * @param eps Numerical tolerance
228 * @return True iff the matrix is of the form
229 * \f$\left[\begin{array}{ccc}
230 a & b & 0 \\
231 -b & a & 0 \\
232 c & d & 1 \end{array}\right]\f$, \f$a^2 + b^2 = 1\f$ and \f$a \neq 1\f$. */
233 14 bool Affine::isNonzeroNonpureRotation(Coord eps) const {
234
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
24 return !are_near(_c[0], 1.0, eps) &&
235
5/6
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 3 times.
29 are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) &&
236 19 are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps);
237 }
238
239 /** @brief For a (possibly non-pure) non-zero-rotation matrix, calculate the rotation center.
240 * @pre The matrix must be a non-zero-rotation matrix to prevent division by zero, see isNonzeroNonpureRotation().
241 * @return The rotation center x, the solution to the equation
242 * \f$A x = x\f$. */
243 2 Point Affine::rotationCenter() const {
244 2 Coord x = (_c[2]*_c[5]+_c[4]-_c[4]*_c[3]) / (1-_c[3]-_c[0]+_c[0]*_c[3]-_c[2]*_c[1]);
245 2 Coord y = (_c[1]*x + _c[5]) / (1 - _c[3]);
246 2 return Point(x,y);
247 };
248
249 /** @brief Check whether this matrix represents pure horizontal shearing.
250 * @param eps Numerical tolerance
251 * @return True iff the matrix is of the form
252 * \f$\left[\begin{array}{ccc}
253 1 & 0 & 0 \\
254 k & 1 & 0 \\
255 0 & 0 & 1 \end{array}\right]\f$. */
256 14 bool Affine::isHShear(Coord eps) const {
257
3/4
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
21 return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
258
5/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
23 are_near(_c[3], 1.0, eps) && are_near(_c[4], 0.0, eps) &&
259 16 are_near(_c[5], 0.0, eps);
260 }
261 /** @brief Check whether this matrix represents pure, nonzero horizontal shearing.
262 * @param eps Numerical tolerance
263 * @return True iff the matrix is of the form
264 * \f$\left[\begin{array}{ccc}
265 1 & 0 & 0 \\
266 k & 1 & 0 \\
267 0 & 0 & 1 \end{array}\right]\f$ and \f$k \neq 0\f$. */
268 14 bool Affine::isNonzeroHShear(Coord eps) const {
269
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
18 return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) &&
270
4/6
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
4 !are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) &&
271
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
19 are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
272 }
273
274 /** @brief Check whether this matrix represents pure vertical shearing.
275 * @param eps Numerical tolerance
276 * @return True iff the matrix is of the form
277 * \f$\left[\begin{array}{ccc}
278 1 & k & 0 \\
279 0 & 1 & 0 \\
280 0 & 0 & 1 \end{array}\right]\f$. */
281 14 bool Affine::isVShear(Coord eps) const {
282
3/4
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
21 return are_near(_c[0], 1.0, eps) && are_near(_c[2], 0.0, eps) &&
283
5/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
23 are_near(_c[3], 1.0, eps) && are_near(_c[4], 0.0, eps) &&
284 16 are_near(_c[5], 0.0, eps);
285 }
286
287 /** @brief Check whether this matrix represents pure, nonzero vertical shearing.
288 * @param eps Numerical tolerance
289 * @return True iff the matrix is of the form
290 * \f$\left[\begin{array}{ccc}
291 1 & k & 0 \\
292 0 & 1 & 0 \\
293 0 & 0 & 1 \end{array}\right]\f$ and \f$k \neq 0\f$. */
294 14 bool Affine::isNonzeroVShear(Coord eps) const {
295
3/4
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
19 return are_near(_c[0], 1.0, eps) && !are_near(_c[1], 0.0, eps) &&
296
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
3 are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) &&
297
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
19 are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps);
298 }
299
300 /** @brief Check whether this matrix represents zooming.
301 * Zooming is any combination of translation and uniform non-flipping scaling.
302 * It preserves angles, ratios of distances between arbitrary points
303 * and unit vectors of line segments.
304 * @param eps Numerical tolerance
305 * @return True iff the matrix is invertible and of the form
306 * \f$\left[\begin{array}{ccc}
307 a & 0 & 0 \\
308 0 & a & 0 \\
309 b & c & 1 \end{array}\right]\f$. */
310 14 bool Affine::isZoom(Coord eps) const {
311
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 12 times.
14 if (isSingular(eps)) return false;
312
6/6
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 3 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 1 times.
12 return are_near(_c[0], _c[3], eps) && are_near(_c[1], 0, eps) && are_near(_c[2], 0, eps);
313 }
314
315 /** @brief Check whether the transformation preserves areas of polygons.
316 * This means that the transformation can be any combination of translation, rotation,
317 * shearing and squeezing (non-uniform scaling such that the absolute value of the product
318 * of Y-scale and X-scale is 1).
319 * @param eps Numerical tolerance
320 * @return True iff \f$|\det A| = 1\f$. */
321 14 bool Affine::preservesArea(Coord eps) const
322 {
323 14 return are_near(descrim2(), 1.0, eps);
324 }
325
326 /** @brief Check whether the transformation preserves angles between lines.
327 * This means that the transformation can be any combination of translation, uniform scaling,
328 * rotation and flipping.
329 * @param eps Numerical tolerance
330 * @return True iff the matrix is of the form
331 * \f$\left[\begin{array}{ccc}
332 a & b & 0 \\
333 -b & a & 0 \\
334 c & d & 1 \end{array}\right]\f$ or
335 \f$\left[\begin{array}{ccc}
336 -a & b & 0 \\
337 b & a & 0 \\
338 c & d & 1 \end{array}\right]\f$. */
339 14 bool Affine::preservesAngles(Coord eps) const
340 {
341
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 12 times.
14 if (isSingular(eps)) return false;
342
6/6
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 4 times.
20 return (are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps)) ||
343
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
20 (are_near(_c[0], -_c[3], eps) && are_near(_c[1], _c[2], eps));
344 }
345
346 /** @brief Check whether the transformation preserves distances between points.
347 * This means that the transformation can be any combination of translation,
348 * rotation and flipping.
349 * @param eps Numerical tolerance
350 * @return True iff the matrix is of the form
351 * \f$\left[\begin{array}{ccc}
352 a & b & 0 \\
353 -b & a & 0 \\
354 c & d & 1 \end{array}\right]\f$ or
355 \f$\left[\begin{array}{ccc}
356 -a & b & 0 \\
357 b & a & 0 \\
358 c & d & 1 \end{array}\right]\f$ and \f$a^2 + b^2 = 1\f$. */
359 14 bool Affine::preservesDistances(Coord eps) const
360 {
361
4/4
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 5 times.
30 return ((are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps)) ||
362
5/6
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 4 times.
32 (are_near(_c[0], -_c[3], eps) && are_near(_c[1], _c[2], eps))) &&
363 23 are_near(_c[0] * _c[0] + _c[1] * _c[1], 1.0, eps);
364 }
365
366 /** @brief Check whether this transformation flips objects.
367 * A transformation flips objects if it has a negative scaling component. */
368 14 bool Affine::flips() const {
369 14 return det() < 0;
370 }
371
372 /** @brief Check whether this matrix is singular.
373 * Singular matrices have no inverse, which means that applying them to a set of points
374 * results in a loss of information.
375 * @param eps Numerical tolerance
376 * @return True iff the determinant is near zero. */
377 1342 bool Affine::isSingular(Coord eps) const {
378 1342 return are_near(det(), 0.0, eps);
379 }
380
381 /** @brief Compute the inverse matrix.
382 * Inverse is a matrix (denoted \f$A^{-1}\f$) such that \f$AA^{-1} = A^{-1}A = I\f$.
383 * Singular matrices have no inverse (for example a matrix that has two of its columns equal).
384 * For such matrices, the identity matrix will be returned instead.
385 * @param eps Numerical tolerance
386 * @return Inverse of the matrix, or the identity matrix if the inverse is undefined.
387 * @post (m * m.inverse()).isIdentity() == true */
388 1243 Affine Affine::inverse() const {
389 1243 Affine d;
390
391 1243 double mx = std::max(fabs(_c[0]) + fabs(_c[1]),
392 2486 fabs(_c[2]) + fabs(_c[3])); // a random matrix norm (either l1 or linfty
393
1/2
✓ Branch 0 taken 1243 times.
✗ Branch 1 not taken.
1243 if(mx > 0) {
394 1243 Geom::Coord const determ = det();
395
2/2
✓ Branch 1 taken 1242 times.
✓ Branch 2 taken 1 times.
1243 if (!rel_error_bound(std::sqrt(fabs(determ)), mx)) {
396 1242 Geom::Coord const ideterm = 1.0 / (determ);
397
398 1242 d._c[0] = _c[3] * ideterm;
399 1242 d._c[1] = -_c[1] * ideterm;
400 1242 d._c[2] = -_c[2] * ideterm;
401 1242 d._c[3] = _c[0] * ideterm;
402 1242 d._c[4] = (-_c[4] * d._c[0] - _c[5] * d._c[2]);
403 1242 d._c[5] = (-_c[4] * d._c[1] - _c[5] * d._c[3]);
404 } else {
405 1 d.setIdentity();
406 }
407 } else {
408 d.setIdentity();
409 }
410
411 1243 return d;
412 }
413
414 /** @brief Calculate the determinant.
415 * @return \f$\det A\f$. */
416 5021 Coord Affine::det() const {
417 // TODO this can overflow
418 5021 return _c[0] * _c[3] - _c[1] * _c[2];
419 }
420
421 /** @brief Calculate the square of the descriminant.
422 * This is simply the absolute value of the determinant.
423 * @return \f$|\det A|\f$. */
424 1221 Coord Affine::descrim2() const {
425 1221 return fabs(det());
426 }
427
428 /** @brief Calculate the descriminant.
429 * If the matrix doesn't contain a shearing or non-uniform scaling component, this value says
430 * how will the length of any line segment change after applying this transformation
431 * to arbitrary objects on a plane. The new length will be
432 * @code line_seg.length() * m.descrim()) @endcode
433 * @return \f$\sqrt{|\det A|}\f$. */
434 1203 Coord Affine::descrim() const {
435 1203 return sqrt(descrim2());
436 }
437
438 /** @brief Combine this transformation with another one.
439 * After this operation, the matrix will correspond to the transformation
440 * obtained by first applying the original version of this matrix, and then
441 * applying @a m. */
442 432941 Affine &Affine::operator*=(Affine const &o) {
443 432941 Coord nc[6];
444
2/2
✓ Branch 0 taken 1298823 times.
✓ Branch 1 taken 432941 times.
1731764 for(int a = 0; a < 5; a += 2) {
445
2/2
✓ Branch 0 taken 2597646 times.
✓ Branch 1 taken 1298823 times.
3896469 for(int b = 0; b < 2; b++) {
446 2597646 nc[a + b] = _c[a] * o._c[b] + _c[a + 1] * o._c[b + 2];
447 }
448 }
449
2/2
✓ Branch 0 taken 2597646 times.
✓ Branch 1 taken 432941 times.
3030587 for(int a = 0; a < 6; ++a) {
450 2597646 _c[a] = nc[a];
451 }
452 432941 _c[4] += o._c[4];
453 432941 _c[5] += o._c[5];
454 432941 return *this;
455 }
456
457 //TODO: What's this!?!
458 /** Given a matrix m such that unit_circle = m*x, this returns the
459 * quadratic form x*A*x = 1.
460 * @relates Affine */
461 Affine elliptic_quadratic_form(Affine const &m) {
462 double od = m[0] * m[1] + m[2] * m[3];
463 Affine ret (m[0]*m[0] + m[1]*m[1], od,
464 od, m[2]*m[2] + m[3]*m[3],
465 0, 0);
466 return ret; // allow NRVO
467 }
468
469 Eigen::Eigen(Affine const &m) {
470 double const B = -m[0] - m[3];
471 double const C = m[0]*m[3] - m[1]*m[2];
472
473 std::vector<double> v = solve_quadratic(1, B, C);
474
475 for (unsigned i = 0; i < v.size(); ++i) {
476 values[i] = v[i];
477 vectors[i] = unit_vector(rot90(Point(m[0] - values[i], m[1])));
478 }
479 for (unsigned i = v.size(); i < 2; ++i) {
480 values[i] = 0;
481 vectors[i] = Point(0,0);
482 }
483 }
484
485 Eigen::Eigen(double m[2][2]) {
486 double const B = -m[0][0] - m[1][1];
487 double const C = m[0][0]*m[1][1] - m[1][0]*m[0][1];
488
489 std::vector<double> v = solve_quadratic(1, B, C);
490
491 for (unsigned i = 0; i < v.size(); ++i) {
492 values[i] = v[i];
493 vectors[i] = unit_vector(rot90(Point(m[0][0] - values[i], m[0][1])));
494 }
495 for (unsigned i = v.size(); i < 2; ++i) {
496 values[i] = 0;
497 vectors[i] = Point(0,0);
498 }
499 }
500
501 /** @brief Nearness predicate for affine transforms.
502 * @returns True if all entries of matrices are within eps of each other.
503 * @relates Affine */
504 6 bool are_near(Affine const &a, Affine const &b, Coord eps)
505 {
506
2/4
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
16 return are_near(a[0], b[0], eps) && are_near(a[1], b[1], eps) &&
507
2/4
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
15 are_near(a[2], b[2], eps) && are_near(a[3], b[3], eps) &&
508
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
16 are_near(a[4], b[4], eps) && are_near(a[5], b[5], eps);
509 }
510
511 } //namespace Geom
512
513 /*
514 Local Variables:
515 mode:c++
516 c-file-style:"stroustrup"
517 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
518 indent-tabs-mode:nil
519 fill-column:99
520 End:
521 */
522 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
523