| 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 |