Code

Implement constrained snapping to nodes
authorDiederik van Lierop <mailat-signdiedenrezidotnl>
Tue, 10 Aug 2010 22:18:26 +0000 (00:18 +0200)
committerDiederik van Lierop <mailat-signdiedenrezidotnl>
Tue, 10 Aug 2010 22:18:26 +0000 (00:18 +0200)
src/draw-context.cpp
src/line-snapper.cpp
src/line-snapper.h
src/object-snapper.cpp
src/object-snapper.h
src/snap.cpp
src/snapper.h

index a531b88d13bc69861932501316fc8beb59abe212..c0ae626d5f436773fda56d0e34cae72e0d33cce6 100644 (file)
@@ -511,7 +511,7 @@ void spdc_endpoint_snap_rotation(SPEventContext const *const ec, Geom::Point &p,
             /* Snap it along best vector */
             SnapManager &m = SP_EVENT_CONTEXT_DESKTOP(ec)->namedview->snap_manager;
             m.setup(SP_EVENT_CONTEXT_DESKTOP(ec));
-            m.constrainedSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE, Inkscape::Snapper::SnapConstraint(best));
+            m.constrainedSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE, Inkscape::Snapper::SnapConstraint(o, best));
         }
     }
 }
index 19e6c0fe67b56108957c937ccee6b24bb4179783..0a1567a47a21454cfa6357acf50b9d9969779a26 100644 (file)
@@ -64,7 +64,8 @@ void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc,
                                                Inkscape::SnapCandidatePoint const &p,
                                                Geom::OptRect const &/*bbox_to_snap*/,
                                                SnapConstraint const &c,
-                                               std::vector<SPItem const *> const */*it*/) const
+                                               std::vector<SPItem const *> const */*it*/,
+                                               std::vector<SnapCandidatePoint> */*unselected_nodes*/) const
 
 {
     if (_snap_enabled == false || _snapmanager->snapprefs.getSnapFrom(p.getSourceType()) == false) {
index 4f3d17998f9fe371612506b1e8c2aed6020dfb60..cdc45c286bce947b2cb86904c88f7095025702db 100644 (file)
@@ -35,7 +35,8 @@ public:
                           Inkscape::SnapCandidatePoint const &p,
                           Geom::OptRect const &bbox_to_snap,
                           SnapConstraint const &c,
-                          std::vector<SPItem const *> const *it) const;
+                          std::vector<SPItem const *> const *it,
+                          std::vector<SnapCandidatePoint> *unselected_nodes) const;
 
 protected:
   typedef std::list<std::pair<Geom::Point, Geom::Point> > LineList;
index d84ee9c4f09db5009019d874be01dd4d9ab60b4e..23af26d47f36d48e291fd8efbb8998249b2006e2 100644 (file)
@@ -257,7 +257,8 @@ void Inkscape::ObjectSnapper::_collectNodes(Inkscape::SnapSourceType const &t,
 
 void Inkscape::ObjectSnapper::_snapNodes(SnappedConstraints &sc,
                                          Inkscape::SnapCandidatePoint const &p,
-                                         std::vector<SnapCandidatePoint> *unselected_nodes) const
+                                         std::vector<SnapCandidatePoint> *unselected_nodes,
+                                         SnapConstraint const &c) const
 {
     // Iterate through all nodes, find out which one is the closest to p, and snap to it!
 
@@ -271,9 +272,20 @@ void Inkscape::ObjectSnapper::_snapNodes(SnappedConstraints &sc,
     bool success = false;
 
     for (std::vector<SnapCandidatePoint>::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) {
-        Geom::Coord dist = Geom::L2((*k).getPoint() - p.getPoint());
+        Geom::Point target_pt = (*k).getPoint();
+        if (!c.isUndefined()) {
+            // We're snapping to nodes along a constraint only, so find out if this node
+            // is at the constraint, while allowing for a small margin
+            if (Geom::L2(target_pt - c.projection(target_pt)) > 1e-9) {
+                // The distance from the target point to its projection on the constraint
+                // is too large, so this point is not on the constraint. Skip it!
+                continue;
+            }
+        }
+
+        Geom::Coord dist = Geom::L2(target_pt - p.getPoint());
         if (dist < getSnapperTolerance() && dist < s.getSnapDistance()) {
-            s = SnappedPoint((*k).getPoint(), p.getSourceType(), p.getSourceNum(), (*k).getTargetType(), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true, (*k).getTargetBBox());
+            s = SnappedPoint(target_pt, p.getSourceType(), p.getSourceNum(), (*k).getTargetType(), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true, (*k).getTargetBBox());
             success = true;
         }
     }
@@ -300,13 +312,13 @@ void Inkscape::ObjectSnapper::_snapTranslatingGuide(SnappedConstraints &sc,
     Geom::Coord tol = getSnapperTolerance();
 
     for (std::vector<SnapCandidatePoint>::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) {
-
+        Geom::Point target_pt = (*k).getPoint();
         // Project each node (*k) on the guide line (running through point p)
-        Geom::Point p_proj = Geom::projection((*k).getPoint(), Geom::Line(p, p + Geom::rot90(guide_normal)));
-        Geom::Coord dist = Geom::L2((*k).getPoint() - p_proj); // distance from node to the guide
+        Geom::Point p_proj = Geom::projection(target_pt, Geom::Line(p, p + Geom::rot90(guide_normal)));
+        Geom::Coord dist = Geom::L2(target_pt - p_proj); // distance from node to the guide
         Geom::Coord dist2 = Geom::L2(p - p_proj); // distance from projection of node on the guide, to the mouse location
         if ((dist < tol && dist2 < tol) || getSnapperAlwaysSnap()) {
-            s = SnappedPoint((*k).getPoint(), SNAPSOURCE_GUIDE, 0, (*k).getTargetType(), dist, tol, getSnapperAlwaysSnap(), false, true, (*k).getTargetBBox());
+            s = SnappedPoint(target_pt, SNAPSOURCE_GUIDE, 0, (*k).getTargetType(), dist, tol, getSnapperAlwaysSnap(), false, true, (*k).getTargetBBox());
             sc.points.push_back(s);
         }
     }
@@ -608,7 +620,7 @@ void Inkscape::ObjectSnapper::freeSnap(SnappedConstraints &sc,
         _findCandidates(sp_document_root(_snapmanager->getDocument()), it, p.getSourceNum() == 0, local_bbox_to_snap, false, Geom::identity());
     }
 
-
+    // TODO: Argh, UGLY! Get rid of this here, move this logic to the snap manager
     bool snap_nodes = (_snapmanager->snapprefs.getSnapModeNode() && (
                             _snapmanager->snapprefs.getSnapToItemNode() ||
                             _snapmanager->snapprefs.getSnapSmoothNodes() ||
@@ -655,7 +667,8 @@ void Inkscape::ObjectSnapper::constrainedSnap( SnappedConstraints &sc,
                                                   Inkscape::SnapCandidatePoint const &p,
                                                   Geom::OptRect const &bbox_to_snap,
                                                   SnapConstraint const &c,
-                                                  std::vector<SPItem const *> const *it) const
+                                                  std::vector<SPItem const *> const *it,
+                                                  std::vector<SnapCandidatePoint> *unselected_nodes) const
 {
     if (_snap_enabled == false || _snapmanager->snapprefs.getSnapFrom(p.getSourceType()) == false) {
         return;
@@ -671,10 +684,24 @@ void Inkscape::ObjectSnapper::constrainedSnap( SnappedConstraints &sc,
     // This is useful for example when scaling an object while maintaining a fixed aspect ratio. It's
     // nodes are only allowed to move in one direction (i.e. in one degree of freedom).
 
-    // When snapping to objects, we either snap to their nodes or their paths. It is however very
-    // unlikely that any node will be exactly at the constrained line, so for a constrained snap
-    // to objects we will only consider the object's paths. Beside, the nodes will be at these paths,
-    // so we will more or less snap to them anyhow.
+    // TODO: Argh, UGLY! Get rid of this here, move this logic to the snap manager
+    bool snap_nodes = (_snapmanager->snapprefs.getSnapModeNode() && (
+                                _snapmanager->snapprefs.getSnapToItemNode() ||
+                                _snapmanager->snapprefs.getSnapSmoothNodes() ||
+                                _snapmanager->snapprefs.getSnapLineMidpoints() ||
+                                _snapmanager->snapprefs.getSnapObjectMidpoints()
+                            )) || (_snapmanager->snapprefs.getSnapModeBBox() && (
+                                _snapmanager->snapprefs.getSnapToBBoxNode() ||
+                                _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() ||
+                                _snapmanager->snapprefs.getSnapBBoxMidpoints()
+                            )) || (_snapmanager->snapprefs.getSnapModeBBoxOrNodes() && (
+                                _snapmanager->snapprefs.getIncludeItemCenter() ||
+                                _snapmanager->snapprefs.getSnapToPageBorder()
+                            ));
+
+    if (snap_nodes) {
+        _snapNodes(sc, p, unselected_nodes, c);
+    }
 
     if (_snapmanager->snapprefs.getSnapToItemPath() || _snapmanager->snapprefs.getSnapToBBoxPath() || _snapmanager->snapprefs.getSnapToPageBorder()) {
         _snapPathsConstrained(sc, p, c);
index 99c8a077ea0bac95b90cf93f3df5aa0ffdb66a33..4933d84598ed72ec1acce5e3ca984c282b5eed59 100644 (file)
@@ -57,7 +57,8 @@ public:
                   Inkscape::SnapCandidatePoint const &p,
                   Geom::OptRect const &bbox_to_snap,
                   SnapConstraint const &c,
-                  std::vector<SPItem const *> const *it) const;
+                  std::vector<SPItem const *> const *it,
+                  std::vector<SnapCandidatePoint> *unselected_nodes) const;
 
 private:
     //store some lists of candidates, points and paths, so we don't have to rebuild them for each point we want to snap
@@ -74,7 +75,8 @@ private:
 
     void _snapNodes(SnappedConstraints &sc,
                       Inkscape::SnapCandidatePoint const &p,
-                      std::vector<SnapCandidatePoint> *unselected_nodes) const; // in desktop coordinates
+                      std::vector<SnapCandidatePoint> *unselected_nodes,
+                      SnapConstraint const &c = SnapConstraint()) const; // in desktop coordinates
 
     void _snapTranslatingGuide(SnappedConstraints &sc,
                      Geom::Point const &p,
index bcacb81e2328416447589344e395ee48408b52d5..7ba85a9aa95855b1f9b784e0171a726771b09dfd 100644 (file)
@@ -374,7 +374,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapCandidatePoint
     SnappedConstraints sc;
     SnapperList const snappers = getSnappers();
     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
-        (*i)->constrainedSnap(sc, p, bbox_to_snap, constraint, &_items_to_ignore);
+        (*i)->constrainedSnap(sc, p, bbox_to_snap, constraint, &_items_to_ignore, _unselected_nodes);
     }
 
     Inkscape::SnappedPoint result = findBestSnap(p, sc, true);
@@ -422,7 +422,7 @@ Inkscape::SnappedPoint SnapManager::multipleConstrainedSnaps(Inkscape::SnapCandi
         // Try to snap to the constraint
         if (!snapping_is_futile) {
             for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
-                (*i)->constrainedSnap(sc, p, bbox_to_snap, *c, &_items_to_ignore);
+                (*i)->constrainedSnap(sc, p, bbox_to_snap, *c, &_items_to_ignore,_unselected_nodes);
             }
         }
     }
@@ -526,14 +526,14 @@ void SnapManager::guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline)
     SnappedConstraints sc;
     Inkscape::Snapper::SnapConstraint cl(guideline.point_on_line, Geom::rot90(guideline.normal_to_line));
     if (object.ThisSnapperMightSnap()) {
-        object.constrainedSnap(sc, candidate, Geom::OptRect(), cl, NULL);
+        object.constrainedSnap(sc, candidate, Geom::OptRect(), cl, NULL, NULL);
     }
 
     // Snap to guides & grid lines
     SnapperList snappers = getGridSnappers();
     snappers.push_back(&guide);
     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
-        (*i)->constrainedSnap(sc, candidate, Geom::OptRect(), cl, NULL);
+        (*i)->constrainedSnap(sc, candidate, Geom::OptRect(), cl, NULL, NULL);
     }
 
     Inkscape::SnappedPoint const s = findBestSnap(candidate, sc, false);
index d8214db8016691d9205e7937b484a47d98e97030..91784d3aefa539adf5751e1b58795a3b0f7e6dae 100644 (file)
@@ -72,7 +72,7 @@ public:
     class SnapConstraint
     {
     private:
-        enum SnapConstraintType {LINE, DIRECTION, CIRCLE};
+        enum SnapConstraintType {LINE, DIRECTION, CIRCLE, UNDEFINED};
 
     public:
         // Constructs a direction constraint, e.g. horizontal or vertical but without a specified point
@@ -82,11 +82,13 @@ public:
         SnapConstraint(Geom::Line const &l) : _point(l.origin()), _direction(l.versor()), _type(LINE) {}
         // Constructs a circular constraint
         SnapConstraint(Geom::Point const &p, Geom::Point const &d, Geom::Coord const &r) : _point(p), _direction(d), _radius(r), _type(CIRCLE) {}
+        // Undefined, or empty constraint
+        SnapConstraint() : _type(UNDEFINED) {}
 
-        bool hasPoint() const {return _type != DIRECTION;}
+        bool hasPoint() const {return _type != DIRECTION && _type != UNDEFINED;}
 
         Geom::Point getPoint() const {
-            g_assert(_type != DIRECTION);
+            g_assert(_type != DIRECTION && _type != UNDEFINED);
             return _point;
         }
 
@@ -102,6 +104,7 @@ public:
         bool isCircular() const { return _type == CIRCLE; }
         bool isLinear() const { return _type == LINE; }
         bool isDirection() const { return _type == DIRECTION; }
+        bool isUndefined() const { return _type == UNDEFINED; }
 
         Geom::Point projection(Geom::Point const &p) const { // returns the projection of p on this constraint
             if (_type == CIRCLE) {
@@ -114,11 +117,14 @@ public:
                     // point to be projected is exactly at the center of the circle, so any point on the circle is a projection
                     return _point + Geom::Point(_radius, 0);
                 }
-            } else {
+            } else if (_type != UNDEFINED){
                 // project on to a linear constraint
                 Geom::Point const p1_on_cl = (_type == LINE) ? _point : p;
                 Geom::Point const p2_on_cl = p1_on_cl + _direction;
                 return Geom::projection(p, Geom::Line(p1_on_cl, p2_on_cl));
+            } else {
+                g_warning("Bug: trying to find the projection onto an undefined constraint");
+                return Geom::Point();
             }
         }
 
@@ -133,7 +139,8 @@ public:
                                  Inkscape::SnapCandidatePoint const &/*p*/,
                                  Geom::OptRect const &/*bbox_to_snap*/,
                                  SnapConstraint const &/*c*/,
-                                 std::vector<SPItem const *> const */*it*/) const {};
+                                 std::vector<SPItem const *> const */*it*/,
+                                 std::vector<SnapCandidatePoint> */*unselected_nodes*/) const {};
 
 protected:
     SnapManager *_snapmanager;