1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
/**
* \file
* \brief 3x3 affine transformation matrix.
*//*
* Authors:
* Lauris Kaplinski <lauris@kaplinski.com> (Original NRAffine definition and related macros)
* Nathan Hurst <njh@mail.csse.monash.edu.au> (Geom::Affine class version of the above)
* Michael G. Sloan <mgsloan@gmail.com> (reorganization and additions)
* Krzysztof Kosiński <tweenk.pl@gmail.com> (removal of boilerplate, docs)
*
* This code is in public domain.
*/
#ifndef LIB2GEOM_SEEN_AFFINE_H
#define LIB2GEOM_SEEN_AFFINE_H
#include <boost/operators.hpp>
#include <2geom/forward.h>
#include <2geom/point.h>
#include <2geom/utils.h>
namespace Geom {
/**
* @brief 3x3 matrix representing an affine transformation.
*
* Affine transformations on elements of a vector space are transformations which can be
* expressed in terms of matrix multiplication followed by addition
* (\f$x \mapsto A x + b\f$). They can be thought of as generalizations of linear functions
* (\f$y = a x + b\f$) to vector spaces. Affine transformations of points on a 2D plane preserve
* the following properties:
*
* - Colinearity: if three points lie on the same line, they will still be colinear after
* an affine transformation.
* - Ratios of distances between points on the same line are preserved
* - Parallel lines remain parallel.
*
* All affine transformations on 2D points can be written as combinations of scaling, rotation,
* shearing and translation. They can be represented as a combination of a vector and a 2x2 matrix,
* but this form is inconvenient to work with. A better solution is to represent all affine
* transformations on the 2D plane as 3x3 matrices, where the last column has fixed values.
* \f[ A = \left[ \begin{array}{ccc}
c_0 & c_1 & 0 \\
c_2 & c_3 & 0 \\
c_4 & c_5 & 1 \end{array} \right]\f]
*
* We then interpret points as row vectors of the form \f$[p_X, p_Y, 1]\f$. Applying a
* transformation to a point can be written as right-multiplication by a 3x3 matrix
* (\f$p' = pA\f$). This subset of matrices is closed under multiplication - combination
* of any two transforms can be expressed as the multiplication of their matrices.
* In this representation, the \f$c_4\f$ and \f$c_5\f$ coefficients represent
* the translation component of the transformation.
*
* Matrices can be multiplied by other more specific transformations. When multiplying,
* the transformations are applied from left to right, so for example <code>m = a * b</code>
* means: @a m first transforms by a, then by b.
*
* @ingroup Transforms
*/
class Affine
: boost::equality_comparable< Affine // generates operator!= from operator==
, boost::multipliable1< Affine
, MultipliableNoncommutative< Affine, Translate
, MultipliableNoncommutative< Affine, Scale
, MultipliableNoncommutative< Affine, Rotate
, MultipliableNoncommutative< Affine, HShear
, MultipliableNoncommutative< Affine, VShear
, MultipliableNoncommutative< Affine, Zoom
> > > > > > > >
{
Coord _c[6];
public:
Affine() {
_c[0] = _c[3] = 1;
_c[1] = _c[2] = _c[4] = _c[5] = 0;
}
/** @brief Create a matrix from its coefficient values.
* It's rather inconvenient to directly create matrices in this way. Use transform classes
* if your transformation has a geometric interpretation.
* @see Translate
* @see Scale
* @see Rotate
* @see HShear
* @see VShear
* @see Zoom */
Affine(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5) {
_c[0] = c0; _c[1] = c1;
_c[2] = c2; _c[3] = c3;
_c[4] = c4; _c[5] = c5;
}
/** @brief Access a coefficient by its index. */
inline Coord operator[](unsigned i) const { return _c[i]; }
inline Coord &operator[](unsigned i) { return _c[i]; }
/// @name Combine with other transformations
/// @{
Affine &operator*=(Affine const &m);
// implemented in transforms.cpp
Affine &operator*=(Translate const &t);
Affine &operator*=(Scale const &s);
Affine &operator*=(Rotate const &r);
Affine &operator*=(HShear const &h);
Affine &operator*=(VShear const &v);
Affine &operator*=(Zoom const &);
/// @}
bool operator==(Affine const &o) const {
for(unsigned i = 0; i < 6; ++i) {
if ( _c[i] != o._c[i] ) return false;
}
return true;
}
/// @name Get the parameters of the matrix's transform
/// @{
Point xAxis() const;
Point yAxis() const;
Point translation() const;
Coord expansionX() const;
Coord expansionY() const;
Point expansion() const { return Point(expansionX(), expansionY()); }
/// @}
/// @name Modify the matrix
/// @{
void setXAxis(Point const &vec);
void setYAxis(Point const &vec);
void setTranslation(Point const &loc);
void setExpansionX(Coord val);
void setExpansionY(Coord val);
void setIdentity();
/// @}
/// @name Inspect the matrix's transform
/// @{
bool isIdentity(Coord eps = EPSILON) const;
bool isTranslation(Coord eps = EPSILON) const;
bool isScale(Coord eps = EPSILON) const;
bool isUniformScale(Coord eps = EPSILON) const;
bool isRotation(Coord eps = EPSILON) const;
bool isHShear(Coord eps = EPSILON) const;
bool isVShear(Coord eps = EPSILON) const;
bool isNonzeroTranslation(Coord eps = EPSILON) const;
bool isNonzeroScale(Coord eps = EPSILON) const;
bool isNonzeroUniformScale(Coord eps = EPSILON) const;
bool isNonzeroRotation(Coord eps = EPSILON) const;
bool isNonzeroNonpureRotation(Coord eps = EPSILON) const;
Point rotationCenter() const;
bool isNonzeroHShear(Coord eps = EPSILON) const;
bool isNonzeroVShear(Coord eps = EPSILON) const;
bool isZoom(Coord eps = EPSILON) const;
bool preservesArea(Coord eps = EPSILON) const;
bool preservesAngles(Coord eps = EPSILON) const;
bool preservesDistances(Coord eps = EPSILON) const;
bool flips() const;
bool isSingular(Coord eps = EPSILON) const;
/// @}
/// @name Compute other matrices
/// @{
Affine withoutTranslation() const {
Affine ret(*this);
ret.setTranslation(Point(0,0));
return ret;
}
Affine inverse() const;
/// @}
/// @name Compute scalar values
/// @{
Coord det() const;
Coord descrim2() const;
Coord descrim() const;
/// @}
inline static Affine identity();
};
/** @brief Print out the Affine (for debugging).
* @relates Affine */
inline std::ostream &operator<< (std::ostream &out_file, const Geom::Affine &m) {
out_file << "A: " << m[0] << " C: " << m[2] << " E: " << m[4] << "\n";
out_file << "B: " << m[1] << " D: " << m[3] << " F: " << m[5] << "\n";
return out_file;
}
// Affine factories
Affine from_basis(const Point &x_basis, const Point &y_basis, const Point &offset=Point(0,0));
Affine elliptic_quadratic_form(Affine const &m);
/** Given a matrix (ignoring the translation) this returns the eigen
* values and vectors. */
class Eigen{
public:
Point vectors[2];
double values[2];
Eigen(Affine const &m);
Eigen(double M[2][2]);
};
/** @brief Create an identity matrix.
* This is a convenience function identical to Affine::identity(). */
inline Affine identity() {
Affine ret(Affine::identity());
return ret; // allow NRVO
}
/** @brief Create an identity matrix.
* @return The matrix
* \f$\left[\begin{array}{ccc}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1 \end{array}\right]\f$.
* @relates Affine */
inline Affine Affine::identity() {
Affine ret(1.0, 0.0,
0.0, 1.0,
0.0, 0.0);
return ret; // allow NRVO
}
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON);
} // end namespace Geom
#endif // LIB2GEOM_SEEN_AFFINE_H
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
|