aa3663acfdbe56b40aaa222e99a0d1bb39480c62
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;
109 }
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
127 {
128 std::list<SPItem const *> lit;
129 lit.push_back(it);
130 return constrainedSnap(t, p, c, lit);
131 }
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
150 {
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;
162 }
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
194 {
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;
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 }
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 }
295 return std::make_pair(best_transformation, best_metric < NR_HUGE);
296 }
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
314 {
315 return _snapTransformed(
316 t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
317 );
318 }
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
339 {
340 return _snapTransformed(
341 t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
342 );
343 }
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
363 {
364 return _snapTransformed(
365 t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
366 );
367 }
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
389 {
390 return _snapTransformed(
391 t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
392 );
393 }
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
417 {
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);
423 }
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
445 {
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);
451 }
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 :