| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * @file | ||
| 3 | * @brief Affine transformation classes | ||
| 4 | *//* | ||
| 5 | * Authors: | ||
| 6 | * ? <?@?.?> | ||
| 7 | * Krzysztof KosiĆski <tweenk.pl@gmail.com> | ||
| 8 | * Johan Engelen | ||
| 9 | * | ||
| 10 | * Copyright ?-2012 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 | #ifndef LIB2GEOM_SEEN_TRANSFORMS_H | ||
| 37 | #define LIB2GEOM_SEEN_TRANSFORMS_H | ||
| 38 | |||
| 39 | #include <cmath> | ||
| 40 | #include <2geom/forward.h> | ||
| 41 | #include <2geom/affine.h> | ||
| 42 | #include <2geom/angle.h> | ||
| 43 | #include <boost/concept/assert.hpp> | ||
| 44 | |||
| 45 | namespace Geom { | ||
| 46 | |||
| 47 | /** @brief Type requirements for transforms. | ||
| 48 | * @ingroup Concepts */ | ||
| 49 | template <typename T> | ||
| 50 | struct TransformConcept { | ||
| 51 | T t, t2; | ||
| 52 | Affine m; | ||
| 53 | Point p; | ||
| 54 | bool bool_; | ||
| 55 | Coord epsilon; | ||
| 56 | void constraints() { | ||
| 57 | m = t; //implicit conversion | ||
| 58 | m *= t; | ||
| 59 | m = m * t; | ||
| 60 | m = t * m; | ||
| 61 | p *= t; | ||
| 62 | p = p * t; | ||
| 63 | t *= t; | ||
| 64 | t = t * t; | ||
| 65 | t = pow(t, 3); | ||
| 66 | bool_ = (t == t); | ||
| 67 | bool_ = (t != t); | ||
| 68 | t = T::identity(); | ||
| 69 | t = t.inverse(); | ||
| 70 | bool_ = are_near(t, t2); | ||
| 71 | bool_ = are_near(t, t2, epsilon); | ||
| 72 | } | ||
| 73 | }; | ||
| 74 | |||
| 75 | /** @brief Base template for transforms. | ||
| 76 | * This class is an implementation detail and should not be used directly. */ | ||
| 77 | template <typename T> | ||
| 78 | class TransformOperations | ||
| 79 | : boost::equality_comparable< T | ||
| 80 | , boost::multipliable< T | ||
| 81 | > > | ||
| 82 | { | ||
| 83 | public: | ||
| 84 | template <typename T2> | ||
| 85 | 803786 | Affine operator*(T2 const &t) const { | |
| 86 | 803786 | Affine ret(*static_cast<T const*>(this)); ret *= t; return ret; | |
| 87 | } | ||
| 88 | }; | ||
| 89 | |||
| 90 | /** @brief Integer exponentiation for transforms. | ||
| 91 | * Negative exponents will yield the corresponding power of the inverse. This function | ||
| 92 | * can also be applied to matrices. | ||
| 93 | * @param t Affine or transform to exponantiate | ||
| 94 | * @param n Exponent | ||
| 95 | * @return \f$A^n\f$ if @a n is positive, \f$(A^{-1})^n\f$ if negative, identity if zero. | ||
| 96 | * @ingroup Transforms */ | ||
| 97 | template <typename T> | ||
| 98 | T pow(T const &t, int n) { | ||
| 99 | BOOST_CONCEPT_ASSERT((TransformConcept<T>)); | ||
| 100 | if (n == 0) return T::identity(); | ||
| 101 | T result(T::identity()); | ||
| 102 | T x(n < 0 ? t.inverse() : t); | ||
| 103 | if (n < 0) n = -n; | ||
| 104 | while ( n ) { // binary exponentiation - fast | ||
| 105 | if ( n & 1 ) { result *= x; --n; } | ||
| 106 | x *= x; n /= 2; | ||
| 107 | } | ||
| 108 | return result; | ||
| 109 | } | ||
| 110 | |||
| 111 | /** @brief Translation by a vector. | ||
| 112 | * @ingroup Transforms */ | ||
| 113 | class Translate | ||
| 114 | : public TransformOperations< Translate > | ||
| 115 | { | ||
| 116 | Point vec; | ||
| 117 | public: | ||
| 118 | /// Create a translation that doesn't do anything. | ||
| 119 | ✗ | Translate() = default; | |
| 120 | /// Construct a translation from its vector. | ||
| 121 | 80637 | explicit Translate(Point const &p) : vec(p) {} | |
| 122 | /// Construct a translation from its coordinates. | ||
| 123 | ✗ | Translate(Coord x, Coord y) : vec(x, y) {} | |
| 124 | |||
| 125 | 71634 | operator Affine() const { return Affine(1, 0, 0, 1, vec[X], vec[Y]); } | |
| 126 | 4356054 | Coord operator[](Dim2 dim) const { return vec[dim]; } | |
| 127 | Coord operator[](unsigned dim) const { return vec[dim]; } | ||
| 128 | ✗ | Translate &operator*=(Translate const &o) { vec += o.vec; return *this; } | |
| 129 | ✗ | bool operator==(Translate const &o) const { return vec == o.vec; } | |
| 130 | |||
| 131 | 1 | Point vector() const { return vec; } | |
| 132 | /// Get the inverse translation. | ||
| 133 | ✗ | Translate inverse() const { return Translate(-vec); } | |
| 134 | /// Get a translation that doesn't do anything. | ||
| 135 | ✗ | static Translate identity() { return {}; } | |
| 136 | |||
| 137 | friend class Point; | ||
| 138 | }; | ||
| 139 | |||
| 140 | inline bool are_near(Translate const &a, Translate const &b, Coord eps = EPSILON) { | ||
| 141 | return are_near(a[X], b[X], eps) && are_near(a[Y], b[Y], eps); | ||
| 142 | } | ||
| 143 | |||
| 144 | /** @brief Scaling from the origin. | ||
| 145 | * During scaling, the point (0,0) will not move. To obtain a scale with a different | ||
| 146 | * invariant point, combine with translation to the origin and back. | ||
| 147 | * @ingroup Transforms */ | ||
| 148 | class Scale | ||
| 149 | : public TransformOperations< Scale > | ||
| 150 | { | ||
| 151 | Point vec = { 1, 1 }; | ||
| 152 | public: | ||
| 153 | /// Create a scaling that doesn't do anything. | ||
| 154 | ✗ | Scale() = default; | |
| 155 | /// Create a scaling from two scaling factors given as coordinates of a point. | ||
| 156 | ✗ | explicit Scale(Point const &p) : vec(p) {} | |
| 157 | /// Create a scaling from two scaling factors. | ||
| 158 | 80079 | Scale(Coord x, Coord y) : vec(x, y) {} | |
| 159 | /// Create an uniform scaling from a single scaling factor. | ||
| 160 | 100 | explicit Scale(Coord s) : vec(s, s) {} | |
| 161 | 104385 | inline operator Affine() const { return Affine(vec[X], 0, 0, vec[Y], 0, 0); } | |
| 162 | |||
| 163 | 61866 | Coord operator[](Dim2 d) const { return vec[d]; } | |
| 164 | Coord operator[](unsigned d) const { return vec[d]; } | ||
| 165 | //TODO: should we keep these mutators? add them to the other transforms? | ||
| 166 | ✗ | Coord &operator[](Dim2 d) { return vec[d]; } | |
| 167 | Coord &operator[](unsigned d) { return vec[d]; } | ||
| 168 | ✗ | Scale &operator*=(Scale const &b) { vec[X] *= b[X]; vec[Y] *= b[Y]; return *this; } | |
| 169 | ✗ | bool operator==(Scale const &o) const { return vec == o.vec; } | |
| 170 | |||
| 171 | Point vector() const { return vec; } | ||
| 172 | ✗ | Scale inverse() const { return Scale(1./vec[0], 1./vec[1]); } | |
| 173 | ✗ | static Scale identity() { return {}; } | |
| 174 | |||
| 175 | friend class Point; | ||
| 176 | }; | ||
| 177 | |||
| 178 | inline bool are_near(Scale const &a, Scale const &b, Coord eps=EPSILON) { | ||
| 179 | return are_near(a[X], b[X], eps) && are_near(a[Y], b[Y], eps); | ||
| 180 | } | ||
| 181 | |||
| 182 | /** @brief Rotation around the origin. | ||
| 183 | * Combine with translations to the origin and back to get a rotation around a different point. | ||
| 184 | * @ingroup Transforms */ | ||
| 185 | class Rotate | ||
| 186 | : public TransformOperations< Rotate > | ||
| 187 | { | ||
| 188 | Point vec = { 1, 0 }; | ||
| 189 | public: | ||
| 190 | /// Construct a zero-degree rotation. | ||
| 191 | 2 | Rotate() = default; | |
| 192 | /** @brief Construct a rotation from its angle in radians. | ||
| 193 | * Positive arguments correspond to counter-clockwise rotations (if Y grows upwards). */ | ||
| 194 | 322 | explicit Rotate(Coord theta) : vec(Point::polar(theta)) {} | |
| 195 | /// Construct a rotation from its characteristic vector. | ||
| 196 | 20124 | explicit Rotate(Point const &p) : vec(p.normalized()) {} | |
| 197 | /// Construct a rotation from the coordinates of its characteristic vector. | ||
| 198 | explicit Rotate(Coord x, Coord y) : Rotate(Point(x, y)) {} | ||
| 199 | 271447 | operator Affine() const { return Affine(vec[X], vec[Y], -vec[Y], vec[X], 0, 0); } | |
| 200 | |||
| 201 | /** @brief Get the characteristic vector of the rotation. | ||
| 202 | * @return A vector that would be obtained by applying this transform to the X versor. */ | ||
| 203 | Point const &vector() const { return vec; } | ||
| 204 | 10000 | Coord angle() const { return atan2(vec); } | |
| 205 | Coord operator[](Dim2 dim) const { return vec[dim]; } | ||
| 206 | Coord operator[](unsigned dim) const { return vec[dim]; } | ||
| 207 | ✗ | Rotate &operator*=(Rotate const &o) { vec *= o; return *this; } | |
| 208 | ✗ | bool operator==(Rotate const &o) const { return vec == o.vec; } | |
| 209 | 10483 | Rotate inverse() const { | |
| 210 | 10483 | Rotate r; | |
| 211 | 10483 | r.vec = Point(vec[X], -vec[Y]); | |
| 212 | 10483 | return r; | |
| 213 | } | ||
| 214 | /// @brief Get a zero-degree rotation. | ||
| 215 | ✗ | static Rotate identity() { return {}; } | |
| 216 | /** @brief Construct a rotation from its angle in degrees. | ||
| 217 | * Positive arguments correspond to clockwise rotations if Y grows downwards. */ | ||
| 218 | ✗ | static Rotate from_degrees(Coord deg) { return Rotate(rad_from_deg(deg)); } | |
| 219 | static Affine around(Point const &p, Coord angle); | ||
| 220 | |||
| 221 | friend class Point; | ||
| 222 | }; | ||
| 223 | |||
| 224 | inline bool are_near(Rotate const &a, Rotate const &b, Coord eps = EPSILON) { | ||
| 225 | return are_near(a[X], b[X], eps) && are_near(a[Y], b[Y], eps); | ||
| 226 | } | ||
| 227 | |||
| 228 | /** @brief Common base for shearing transforms. | ||
| 229 | * This class is an implementation detail and should not be used directly. | ||
| 230 | * @ingroup Transforms */ | ||
| 231 | template <typename S> | ||
| 232 | class ShearBase | ||
| 233 | : public TransformOperations<S> | ||
| 234 | { | ||
| 235 | protected: | ||
| 236 | Coord f = 0; | ||
| 237 | ✗ | ShearBase() = default; | |
| 238 | explicit ShearBase(Coord _f) : f(_f) {} | ||
| 239 | public: | ||
| 240 | Coord factor() const { return f; } | ||
| 241 | void setFactor(Coord nf) { f = nf; } | ||
| 242 | ✗ | S &operator*=(S const &s) { f += s.f; return static_cast<S &>(*this); } | |
| 243 | bool operator==(ShearBase<S> const &s) const { return f == s.f; } | ||
| 244 | S inverse() const { return S(-f); } | ||
| 245 | ✗ | static S identity() { return {}; } | |
| 246 | |||
| 247 | friend class Point; | ||
| 248 | friend class Affine; | ||
| 249 | }; | ||
| 250 | |||
| 251 | /** @brief Horizontal shearing. | ||
| 252 | * Points on the X axis will not move. Combine with translations to get a shear | ||
| 253 | * with a different invariant line. | ||
| 254 | * @ingroup Transforms */ | ||
| 255 | class HShear | ||
| 256 | : public ShearBase<HShear> | ||
| 257 | { | ||
| 258 | public: | ||
| 259 | ✗ | HShear() = default; | |
| 260 | explicit HShear(Coord h) : ShearBase<HShear>(h) {} | ||
| 261 | ✗ | operator Affine() const { return Affine(1, 0, f, 1, 0, 0); } | |
| 262 | }; | ||
| 263 | |||
| 264 | inline bool are_near(HShear const &a, HShear const &b, Coord eps=EPSILON) { | ||
| 265 | return are_near(a.factor(), b.factor(), eps); | ||
| 266 | } | ||
| 267 | |||
| 268 | /** @brief Vertical shearing. | ||
| 269 | * Points on the Y axis will not move. Combine with translations to get a shear | ||
| 270 | * with a different invariant line. | ||
| 271 | * @ingroup Transforms */ | ||
| 272 | class VShear | ||
| 273 | : public ShearBase<VShear> | ||
| 274 | { | ||
| 275 | public: | ||
| 276 | ✗ | VShear() = default; | |
| 277 | explicit VShear(Coord h) : ShearBase<VShear>(h) {} | ||
| 278 | ✗ | operator Affine() const { return Affine(1, f, 0, 1, 0, 0); } | |
| 279 | }; | ||
| 280 | |||
| 281 | inline bool are_near(VShear const &a, VShear const &b, Coord eps = EPSILON) { | ||
| 282 | return are_near(a.factor(), b.factor(), eps); | ||
| 283 | } | ||
| 284 | |||
| 285 | /** @brief Combination of a translation and uniform scale. | ||
| 286 | * The translation part is applied first, then the result is scaled from the new origin. | ||
| 287 | * This way when the class is used to accumulate a zoom transform, trans always points | ||
| 288 | * to the new origin in original coordinates. | ||
| 289 | * @ingroup Transforms */ | ||
| 290 | class Zoom | ||
| 291 | : public TransformOperations< Zoom > | ||
| 292 | { | ||
| 293 | Coord _scale = 1; | ||
| 294 | Point _trans; | ||
| 295 | public: | ||
| 296 | ✗ | Zoom() = default; | |
| 297 | /// Construct a zoom from a scaling factor. | ||
| 298 | explicit Zoom(Coord s) : _scale(s) {} | ||
| 299 | /// Construct a zoom from a translation. | ||
| 300 | explicit Zoom(Point const &t) : _trans(t) {} | ||
| 301 | explicit Zoom(Translate const &t) : Zoom(t.vector()) {} | ||
| 302 | /// Construct a zoom from a scaling factor and a translation. | ||
| 303 | 2 | Zoom(Coord s, Point const &t) : _scale(s), _trans(t) {} | |
| 304 | 1 | Zoom(Coord s, Translate const &t) : Zoom(s, t.vector()) {} | |
| 305 | |||
| 306 | ✗ | operator Affine() const { | |
| 307 | ✗ | return Affine(_scale, 0, 0, _scale, _trans[X] * _scale, _trans[Y] * _scale); | |
| 308 | } | ||
| 309 | ✗ | Zoom &operator*=(Zoom const &z) { | |
| 310 | ✗ | _trans += z._trans / _scale; | |
| 311 | ✗ | _scale *= z._scale; | |
| 312 | ✗ | return *this; | |
| 313 | } | ||
| 314 | bool operator==(Zoom const &z) const { return _scale == z._scale && _trans == z._trans; } | ||
| 315 | |||
| 316 | ✗ | Coord scale() const { return _scale; } | |
| 317 | void setScale(Coord s) { _scale = s; } | ||
| 318 | Point translation() const { return _trans; } | ||
| 319 | void setTranslation(Point const &p) { _trans = p; } | ||
| 320 | Zoom inverse() const { return Zoom(1 / _scale, Translate(-_trans * _scale)); } | ||
| 321 | ✗ | static Zoom identity() { return {}; } | |
| 322 | static Zoom map_rect(Rect const &old_r, Rect const &new_r); | ||
| 323 | |||
| 324 | friend class Point; | ||
| 325 | friend class Affine; | ||
| 326 | }; | ||
| 327 | |||
| 328 | inline bool are_near(Zoom const &a, Zoom const &b, Coord eps = EPSILON) { | ||
| 329 | return are_near(a.scale(), b.scale(), eps) && | ||
| 330 | are_near(a.translation(), b.translation(), eps); | ||
| 331 | } | ||
| 332 | |||
| 333 | /** @brief Specialization of exponentiation for Scale. | ||
| 334 | * @relates Scale */ | ||
| 335 | template<> | ||
| 336 | inline Scale pow(Scale const &s, int n) { | ||
| 337 | return Scale(::pow(s[X], n), ::pow(s[Y], n)); | ||
| 338 | } | ||
| 339 | /** @brief Specialization of exponentiation for Translate. | ||
| 340 | * @relates Translate */ | ||
| 341 | template<> | ||
| 342 | inline Translate pow(Translate const &t, int n) { | ||
| 343 | return Translate(t[X] * n, t[Y] * n); | ||
| 344 | } | ||
| 345 | |||
| 346 | /** @brief Reflects objects about line. | ||
| 347 | * The line, defined by a vector along the line and a point on it, acts as a mirror. | ||
| 348 | * @ingroup Transforms | ||
| 349 | * @see Line::reflection() | ||
| 350 | */ | ||
| 351 | Affine reflection(Point const & vector, Point const & origin); | ||
| 352 | |||
| 353 | //TODO: decomposition of Affine into some finite combination of the above classes | ||
| 354 | |||
| 355 | } // namespace Geom | ||
| 356 | |||
| 357 | #endif // LIB2GEOM_SEEN_TRANSFORMS_H | ||
| 358 | |||
| 359 | /* | ||
| 360 | Local Variables: | ||
| 361 | mode:c++ | ||
| 362 | c-file-style:"stroustrup" | ||
| 363 | c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) | ||
| 364 | indent-tabs-mode:nil | ||
| 365 | fill-column:99 | ||
| 366 | End: | ||
| 367 | */ | ||
| 368 | // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : | ||
| 369 |