Code

aa3663acfdbe56b40aaa222e99a0d1bb39480c62
[inkscape.git] / src / snap.cpp
1 #define __SP_DESKTOP_SNAP_C__
3 /**
4  * \file snap.cpp
5  *
6  * \brief Various snapping methods
7  *
8  * Authors:
9  *   Lauris Kaplinski <lauris@kaplinski.com>
10  *   Frank Felfe <innerspace@iname.com>
11  *   Carl Hetherington <inkscape@carlh.net>
12  *
13  * Copyright (C) 1999-2002 Authors
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 #include "sp-namedview.h"
19 #include "snap.h"
20 #include <libnr/nr-point-fns.h>
21 #include <libnr/nr-scale-ops.h>
22 #include <libnr/nr-values.h>
24 /**
25  *  Construct a SnapManager for a SPNamedView.
26  *
27  *  \param v `Owning' SPNamedView.
28  */
30 SnapManager::SnapManager(SPNamedView const *v) : grid(v, 0), guide(v, 0), object(v, 0)
31 {
33 }
36 /**
37  *  \return List of snappers that we use.
38  */
40 SnapManager::SnapperList SnapManager::getSnappers() const
41 {
42     SnapManager::SnapperList s;
43     s.push_back(&grid);
44     s.push_back(&guide);
45     s.push_back(&object);
46     return s;
47 }
49 /**
50  * \return true if one of the snappers will try to snap something.
51  */
53 bool SnapManager::willSnapSomething() const
54 {
55     SnapperList const s = getSnappers();
56     SnapperList::const_iterator i = s.begin();
57     while (i != s.end() && (*i)->willSnapSomething() == false) {
58         i++;
59     }
61     return (i != s.end());
62 }
65 /**
66  *  Try to snap a point to any interested snappers.
67  *
68  *  \param t Type of point.
69  *  \param p Point.
70  *  \param it Item to ignore when snapping.
71  *  \return Snapped point.
72  */
74 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
75                                              NR::Point const &p,
76                                              SPItem const *it) const
78 {
79     std::list<SPItem const *> lit;
80     lit.push_back(it);
81     return freeSnap(t, p, lit);
82 }
85 /**
86  *  Try to snap a point to any interested snappers.
87  *
88  *  \param t Type of point.
89  *  \param p Point.
90  *  \param it List of items to ignore when snapping.
91  *  \return Snapped point.
92  */
94 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
95                                              NR::Point const &p,
96                                              std::list<SPItem const *> const &it) const
97 {
98     Inkscape::SnappedPoint r(p, NR_HUGE);
100     SnapperList const snappers = getSnappers();
101     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
102         Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
103         if (s.getDistance() < r.getDistance()) {
104             r = s;
105         }
106     }
108     return r;
112 /**
113  *  Try to snap a point to any interested snappers.  A snap will only occur along
114  *  a line described by a Inkscape::Snapper::ConstraintLine.
115  *
116  *  \param t Type of point.
117  *  \param p Point.
118  *  \param c Constraint line.
119  *  \param it Item to ignore when snapping.
120  *  \return Snapped point.
121  */
123 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
124                                                     NR::Point const &p,
125                                                     Inkscape::Snapper::ConstraintLine const &c,
126                                                     SPItem const *it) const
128     std::list<SPItem const *> lit;
129     lit.push_back(it);
130     return constrainedSnap(t, p, c, lit);
135 /**
136  *  Try to snap a point to any interested snappers.  A snap will only occur along
137  *  a line described by a Inkscape::Snapper::ConstraintLine.
138  *
139  *  \param t Type of point.
140  *  \param p Point.
141  *  \param c Constraint line.
142  *  \param it List of items to ignore when snapping.
143  *  \return Snapped point.
144  */
146 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
147                                                     NR::Point const &p,
148                                                     Inkscape::Snapper::ConstraintLine const &c,
149                                                     std::list<SPItem const *> const &it) const
151     Inkscape::SnappedPoint r(p, NR_HUGE);
153     SnapperList const snappers = getSnappers();
154     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
155         Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
156         if (s.getDistance() < r.getDistance()) {
157             r = s;
158         }
159     }
161     return r;
166 /**
167  *  Main internal snapping method, which is called by the other, friendlier, public
168  *  methods.  It's a bit hairy as it has lots of parameters, but it saves on a lot
169  *  of duplicated code.
170  *
171  *  \param type Type of points being snapped.
172  *  \param points List of points to snap.
173  *  \param ignore List of items to ignore while snapping.
174  *  \param constrained true if the snap is constrained.
175  *  \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
176  *  \param transformation_type Type of transformation to apply to points before trying to snap them.
177  *  \param transformation Description of the transformation; details depend on the type.
178  *  \param origin Origin of the transformation, if applicable.
179  *  \param dim Dimension of the transformation, if applicable.
180  *  \param uniform true if the transformation should be uniform, if applicable.
181  */
183 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
184     Inkscape::Snapper::PointType type,
185     std::vector<NR::Point> const &points,
186     std::list<SPItem const *> const &ignore,
187     bool constrained,
188     Inkscape::Snapper::ConstraintLine const &constraint,
189     Transformation transformation_type,
190     NR::Point const &transformation,
191     NR::Point const &origin,
192     NR::Dim2 dim,
193     bool uniform) const
195     /* We have a list of points, which we are proposing to transform in some way.  We need to see
196     ** if any of these points, when transformed, snap to anything.  If they do, we return the
197     ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
198     */
200     /* Quick check to see if we have any snappers that are enabled */
201     if (willSnapSomething() == false) {
202         return std::make_pair(transformation, false);
203     }
205     /* The current best transformation */
206     NR::Point best_transformation = transformation;
207     
208     /* The current best metric for the best transformation; lower is better, NR_HUGE
209     ** means that we haven't snapped anything.
210     */
211     double best_metric = NR_HUGE;
213     for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
215         /* Work out the transformed version of this point */
216         NR::Point transformed;
217         switch (transformation_type) {
218             case TRANSLATION:
219                 transformed = *i + transformation;
220                 break;
221             case SCALE:
222                 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
223                 break;
224             case STRETCH:
225             {
226                 NR::scale s(1, 1);
227                 if (uniform)
228                     s[NR::X] = s[NR::Y] = transformation[dim];
229                 else {
230                     s[dim] = transformation[dim];
231                     s[1 - dim] = 1;
232                 }
233                 transformed = ((*i - origin) * s) + origin;
234                 break;
235             }
236             case SKEW:
237                 transformed = *i;
238                 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
239                 break;
240             default:
241                 g_assert_not_reached();
242         }
243         
244         /* Snap it */
245         Inkscape::SnappedPoint const snapped = constrained ?
246             constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
248         if (snapped.getDistance() < NR_HUGE) {
249             /* We snapped.  Find the transformation that describes where the snapped point has
250             ** ended up, and also the metric for this transformation.
251             */
252             NR::Point result;
253             NR::Coord metric;
254             switch (transformation_type) {
255                 case TRANSLATION:
256                     result = snapped.getPoint() - *i;
257                     metric = NR::L2(result);
258                     break;
259                 case SCALE:
260                 {
261                     NR::Point const a = (snapped.getPoint() - origin);
262                     NR::Point const b = (*i - origin);
263                     result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
264                     metric = std::abs(NR::L2(result) - NR::L2(transformation));
265                     break;
266                 }
267                 case STRETCH:
268                 {
269                     for (int j = 0; j < 2; j++) {
270                         if (uniform || j == dim) {
271                             result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
272                         } else {
273                             result[j] = 1;
274                         }
275                     }
276                     metric = std::abs(result[dim] - transformation[dim]);
277                     break;
278                 }
279                 case SKEW:
280                     result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
281                     metric = std::abs(result[dim] - transformation[dim]);
282                     break;
283                 default:
284                     g_assert_not_reached();
285             }
287             /* Note it if it's the best so far */
288             if (metric < best_metric && metric != 0) {
289                 best_transformation = result;
290                 best_metric = metric;
291             }
292         }
293     }
294         
295     return std::make_pair(best_transformation, best_metric < NR_HUGE);
299 /**
300  *  Try to snap a list of points to any interested snappers after they have undergone
301  *  a translation.
302  *
303  *  \param t Type of points.
304  *  \param p Points.
305  *  \param it List of items to ignore when snapping.
306  *  \param tr Proposed translation.
307  *  \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
308  */
310 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
311                                                             std::vector<NR::Point> const &p,
312                                                             std::list<SPItem const *> const &it,
313                                                             NR::Point const &tr) const
315     return _snapTransformed(
316         t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
317         );
321 /**
322  *  Try to snap a list of points to any interested snappers after they have undergone a
323  *  translation.  A snap will only occur along a line described by a
324  *  Inkscape::Snapper::ConstraintLine.
325  *
326  *  \param t Type of points.
327  *  \param p Points.
328  *  \param it List of items to ignore when snapping.
329  *  \param c Constraint line.
330  *  \param tr Proposed translation.
331  *  \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
332  */
334 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
335                                                                    std::vector<NR::Point> const &p,
336                                                                    std::list<SPItem const *> const &it,
337                                                                    Inkscape::Snapper::ConstraintLine const &c,
338                                                                    NR::Point const &tr) const
340     return _snapTransformed(
341         t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
342         );
346 /**
347  *  Try to snap a list of points to any interested snappers after they have undergone
348  *  a scale.
349  *
350  *  \param t Type of points.
351  *  \param p Points.
352  *  \param it List of items to ignore when snapping.
353  *  \param s Proposed scale.
354  *  \param o Origin of proposed scale.
355  *  \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
356  */
358 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
359                                                       std::vector<NR::Point> const &p,
360                                                       std::list<SPItem const *> const &it,
361                                                       NR::scale const &s,
362                                                       NR::Point const &o) const
364     return _snapTransformed(
365         t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
366         );
370 /**
371  *  Try to snap a list of points to any interested snappers after they have undergone
372  *  a scale.  A snap will only occur along a line described by a
373  *  Inkscape::Snapper::ConstraintLine.
374  *
375  *  \param t Type of points.
376  *  \param p Points.
377  *  \param it List of items to ignore when snapping.
378  *  \param s Proposed scale.
379  *  \param o Origin of proposed scale.
380  *  \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
381  */
383 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
384                                                              std::vector<NR::Point> const &p,
385                                                              std::list<SPItem const *> const &it,
386                                                              Inkscape::Snapper::ConstraintLine const &c,
387                                                              NR::scale const &s,
388                                                              NR::Point const &o) const
390     return _snapTransformed(
391         t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
392         );
396 /**
397  *  Try to snap a list of points to any interested snappers after they have undergone
398  *  a stretch.
399  *
400  *  \param t Type of points.
401  *  \param p Points.
402  *  \param it List of items to ignore when snapping.
403  *  \param s Proposed stretch.
404  *  \param o Origin of proposed stretch.
405  *  \param d Dimension in which to apply proposed stretch.
406  *  \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
407  *  \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
408  */
410 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
411                                                         std::vector<NR::Point> const &p,
412                                                         std::list<SPItem const *> const &it,
413                                                         NR::Coord const &s,
414                                                         NR::Point const &o,
415                                                         NR::Dim2 d,
416                                                         bool u) const
418    std::pair<NR::Point, bool> const r = _snapTransformed(
419         t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
420         );
422    return std::make_pair(r.first[d], r.second);
426 /**
427  *  Try to snap a list of points to any interested snappers after they have undergone
428  *  a skew.
429  *
430  *  \param t Type of points.
431  *  \param p Points.
432  *  \param it List of items to ignore when snapping.
433  *  \param s Proposed skew.
434  *  \param o Origin of proposed skew.
435  *  \param d Dimension in which to apply proposed skew.
436  *  \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
437  */
439 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
440                                                      std::vector<NR::Point> const &p,
441                                                      std::list<SPItem const *> const &it,
442                                                      NR::Coord const &s,
443                                                      NR::Point const &o,
444                                                      NR::Dim2 d) const
446    std::pair<NR::Point, bool> const r = _snapTransformed(
447         t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
448         );
450    return std::make_pair(r.first[d], r.second);
453 /*
454   Local Variables:
455   mode:c++
456   c-file-style:"stroustrup"
457   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
458   indent-tabs-mode:nil
459   fill-column:99
460   End:
461 */
462 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :