Code

Snap to intersections of line segments
[inkscape.git] / src / snap.cpp
index 1529dc40d7ab21c50dc6f5aebc228237e678cf77..edc30e21a13ca1feae8fbd23f0993364eb551dbf 100644 (file)
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
+#include <utility>
+
 #include "sp-namedview.h"
 #include "snap.h"
+#include "snapped-line.h"
 
 #include <libnr/nr-point-fns.h>
 #include <libnr/nr-scale-ops.h>
@@ -43,7 +46,7 @@ SnapManager::SnapManager(SPNamedView const *v) :
     _named_view(v),
     _include_item_center(false)
 {
-       
+    
 }
 
 
@@ -101,7 +104,7 @@ bool SnapManager::SomeSnapperMightSnap() const
 /*
  *  The snappers have too many parameters to adjust individually. Therefore only
  *  two snapping modes are presented to the user: snapping bounding box corners (to 
- *     other bounding boxes, grids or guides), and/or snapping nodes (to other nodes,
+ *  other bounding boxes, grids or guides), and/or snapping nodes (to other nodes,
  *  paths, grids or guides). To select either of these modes (or both), use the 
  *  methods defined below: setSnapModeBBox() and setSnapModeNode().
  * 
@@ -110,53 +113,53 @@ bool SnapManager::SomeSnapperMightSnap() const
 
 void SnapManager::setSnapModeBBox(bool enabled)
 {
-       //The default values are being set in sp_namedview_set() (in sp-namedview.cpp)
-       guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
-       
-       for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
+    //The default values are being set in sp_namedview_set() (in sp-namedview.cpp)
+    guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
+    
+    for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
         grid->snapper->setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
     }
-       
-       object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
-       object.setSnapToBBoxNode(enabled);
-       object.setSnapToBBoxPath(enabled);
-       object.setStrictSnapping(true); //don't snap bboxes to nodes/paths and vice versa       
+    
+    object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
+    //object.setSnapToBBoxNode(enabled); // On second thought, these should be controlled
+    //object.setSnapToBBoxPath(enabled); // separately by the snapping prefs dialog
+    object.setStrictSnapping(true); //don't snap bboxes to nodes/paths and vice versa    
 }
 
 bool SnapManager::getSnapModeBBox() const
 {
-       return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX);
+    return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX);
 }
 
 void SnapManager::setSnapModeNode(bool enabled)
 {
-       guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
-       
-       for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
+    guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
+    
+    for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
         grid->snapper->setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
     }
-       
-       object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
-       //object.setSnapToItemNode(enabled); // On second thought, these should be controlled
-       //object.setSnapToItemPath(enabled); // separately by the snapping prefs dialog 
-       object.setStrictSnapping(true);
+        
+    object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
+    //object.setSnapToItemNode(enabled); // On second thought, these should be controlled
+    //object.setSnapToItemPath(enabled); // separately by the snapping prefs dialog 
+    object.setStrictSnapping(true);
 }
 
 bool SnapManager::getSnapModeNode() const
 {
-       return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE);
+    return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE);
 }
 
 void SnapManager::setSnapModeGuide(bool enabled)
 {
-       object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_GUIDE, enabled);
+    object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_GUIDE, enabled);
 }
 
 bool SnapManager::getSnapModeGuide() const
 {
-       return object.getSnapFrom(Inkscape::Snapper::SNAPPOINT_GUIDE);
+    return object.getSnapFrom(Inkscape::Snapper::SNAPPOINT_GUIDE);
 }
 
 /**
@@ -182,29 +185,6 @@ Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
     return freeSnap(t, p, true, points_to_snap, lit);
 }
 
-
-/**
- *  Try to snap a point to any interested snappers.
- *
- *  \param t Type of point.
- *  \param p Point.
- *  \param first_point If true then this point is the first one from a whole bunch of points 
- *  \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation 
- *  \param it List of items to ignore when snapping.
- *  \return Snapped point.
- */
- Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
-                                             NR::Point const &p,
-                                             bool const &first_point,
-                                             std::vector<NR::Point> &points_to_snap,
-                                             std::list<SPItem const *> const &it) const
-{
-    SnapperList const snappers = getSnappers();
-
-       return freeSnap(t, p, first_point, points_to_snap, it, snappers);
-}
-
 /**
  *  Try to snap a point to any of the specified snappers.
  *
@@ -221,19 +201,18 @@ Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
                                              NR::Point const &p,
                                              bool const &first_point,
                                              std::vector<NR::Point> &points_to_snap,
-                                             std::list<SPItem const *> const &it,
-                                             SnapperList const &snappers) const
+                                             std::list<SPItem const *> const &it) const
 {
-    Inkscape::SnappedPoint r(p, NR_HUGE);
+    
+    SnappedConstraints sc;        
+    
+    SnapperList const snappers = getSnappers();
 
     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
-        Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, first_point, points_to_snap, it);
-        if (s.getDistance() < r.getDistance()) {
-            r = s;
-        }
+        (*i)->freeSnap(sc, t, p, first_point, points_to_snap, it);
     }
 
-    return r;
+    return findBestSnap(p, sc);
 }
 
 /**
@@ -273,22 +252,19 @@ SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
                              std::list<SPItem const *> const &it,
                              SnapperList &snappers )
 {
-    Inkscape::SnappedPoint r(p, NR_HUGE);
+    
+    SnappedConstraints sc;                
 
     for (SnapperList::iterator i = snappers.begin(); i != snappers.end(); i++) {
         gdouble const curr_gridsnap = (*i)->getDistance();
         const_cast<Inkscape::Snapper*> (*i)->setDistance(NR_HUGE);
         std::vector<NR::Point> points_to_snap;
-       points_to_snap.push_back(p);    
-        Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, true, points_to_snap, it);
+        points_to_snap.push_back(p);    
+        (*i)->freeSnap(sc, t, p, true, points_to_snap, it);
         const_cast<Inkscape::Snapper*> (*i)->setDistance(curr_gridsnap);
-
-        if (s.getDistance() < r.getDistance()) {
-            r = s;
-        }
     }
 
-    return r;
+    return findBestSnap(p, sc);
 }
 
 
@@ -336,37 +312,38 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType
 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
                                                     NR::Point const &p,
                                                     bool const &first_point,
-                                                       std::vector<NR::Point> &points_to_snap,
+                                                     std::vector<NR::Point> &points_to_snap,
                                                     Inkscape::Snapper::ConstraintLine const &c,
                                                     std::list<SPItem const *> const &it) const
 {
-    Inkscape::SnappedPoint r(p, NR_HUGE);
-
+    
+    SnappedConstraints sc;
+        
     SnapperList const snappers = getSnappers();
     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
-        Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, first_point, points_to_snap, c, it);
-        if (s.getDistance() < r.getDistance()) {
-            r = s;
-        }
+        (*i)->constrainedSnap(sc, t, p, first_point, points_to_snap, c, it);
     }
 
-    return r;
+    return findBestSnap(p, sc);
 }
 
 Inkscape::SnappedPoint SnapManager::guideSnap(NR::Point const &p,
-                                                       NR::Point const &guide_normal) const
+                                             NR::Point const &guide_normal) const
 {
-       Inkscape::ObjectSnapper::DimensionToSnap snap_dim;
-       if (guide_normal == component_vectors[NR::Y]) {
-               snap_dim = Inkscape::ObjectSnapper::SNAP_Y;
-       } else if (guide_normal == component_vectors[NR::X]) {
-               snap_dim = Inkscape::ObjectSnapper::SNAP_X;
-       } else {
-               g_warning("WARNING: snapping of angled guides is not supported yet!");
-               snap_dim = Inkscape::ObjectSnapper::SNAP_XY;
-       }
-       
-       return object.guideSnap(p, snap_dim);   
+    Inkscape::ObjectSnapper::DimensionToSnap snap_dim;
+    if (guide_normal == component_vectors[NR::Y]) {
+        snap_dim = Inkscape::ObjectSnapper::SNAP_Y;
+    } else if (guide_normal == component_vectors[NR::X]) {
+        snap_dim = Inkscape::ObjectSnapper::SNAP_X;
+    } else {
+        g_warning("WARNING: snapping of angled guides is not supported yet!");
+        snap_dim = Inkscape::ObjectSnapper::SNAP_XY;
+    }
+    
+    SnappedConstraints sc;
+    object.guideSnap(sc, p, snap_dim);
+    
+    return findBestSnap(p, sc);    
 }
 
 
@@ -454,7 +431,9 @@ std::pair<NR::Point, bool> SnapManager::_snapTransformed(
     */
     double best_metric = NR_HUGE;
 
-       std::vector<NR::Point>::const_iterator j = transformed_points.begin();
+    std::vector<NR::Point>::const_iterator j = transformed_points.begin();
+
+    // std::cout << std::endl;
 
     for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
         
@@ -462,16 +441,26 @@ std::pair<NR::Point, bool> SnapManager::_snapTransformed(
         Inkscape::SnappedPoint const snapped = constrained ?
             constrainedSnap(type, *j, i == points.begin(), transformed_points, constraint, ignore) : freeSnap(type, *j, i == points.begin(), transformed_points, ignore);
 
+        NR::Point result;
+        NR::Coord metric;
+        
         if (snapped.getDistance() < NR_HUGE) {
             /* We snapped.  Find the transformation that describes where the snapped point has
             ** ended up, and also the metric for this transformation.
             */
-            NR::Point result;
-            NR::Coord metric;
             switch (transformation_type) {
                 case TRANSLATION:
                     result = snapped.getPoint() - *i;
-                    metric = NR::L2(result);
+                    /* Consider the case in which a box is almost aligned with a grid in both 
+                     * horizontal and vertical directions. The distance to the intersection of
+                     * the grid lines will always be larger then the distance to a single grid
+                     * line. If we prefer snapping to an intersection instead of to a single 
+                     * grid line, then we cannot use "metric = NR::L2(result)". Therefore the
+                     * snapped distance will be used as a metric. Please note that the snapped
+                     * distance is defined as the distance to the nearest line of the intersection,
+                     * and not to the intersection itself! 
+                     */
+                    metric = snapped.getDistance(); //used to be: metric = NR::L2(result);
                     break;
                 case SCALE:
                 {
@@ -483,11 +472,11 @@ std::pair<NR::Point, bool> SnapManager::_snapTransformed(
                 }
                 case STRETCH:
                 {
-                    for (int j = 0; j < 2; j++) {
-                        if (uniform || j == dim) {
-                            result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
+                    for (int a = 0; a < 2; a++) {
+                        if (uniform || a == dim) {
+                            result[a] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
                         } else {
-                            result[j] = 1;
+                            result[a] = 1;
                         }
                     }
                     metric = std::abs(result[dim] - transformation[dim]);
@@ -502,15 +491,17 @@ std::pair<NR::Point, bool> SnapManager::_snapTransformed(
             }
 
             /* Note it if it's the best so far */
-            if (metric < best_metric) {
+            if ((metric < best_metric) || ((metric == best_metric) && snapped.getAtIntersection() == true)) {
                 best_transformation = result;
                 best_metric = metric;
-            }
+                // std::cout << "SEL ";;
+            } //else { std::cout << "    ";}
         }
         
+        // std::cout << "P_orig = " << (*i) << " | metric = " << metric << " | distance = " << snapped.getDistance() << " | P_snap = " << snapped.getPoint() << std::endl;
         j++;
     }
-
+    
     // Using " < 1e6" instead of " < NR::HUGE" for catching some rounding errors
     // These rounding errors might be caused by NRRects, see bug #1584301
     return std::make_pair(best_transformation, best_metric < 1e6);
@@ -671,6 +662,98 @@ std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointTyp
    return std::make_pair(r.first[d], r.second);
 }
 
+Inkscape::SnappedPoint SnapManager::findBestSnap(NR::Point const &p, SnappedConstraints &sc) const
+{
+    NR::Coord const guide_sens = guide.getDistance();
+    NR::Coord grid_sens = 0;
+    
+    SnapManager::SnapperList const gs = getGridSnappers();
+    SnapperList::const_iterator i = gs.begin();
+    if (i != gs.end()) {        
+        grid_sens = (*i)->getDistance();
+    }
+    
+    // Store all snappoints, optionally together with their specific snapping range
+    std::list<std::pair<Inkscape::SnappedPoint, NR::Coord> > sp_list;
+    // Most of these snapped points are already within the snapping range, because
+    // they have already been filtered by their respective snappers. In that case
+    // we can set the snapping range to NR_HUGE here. If however we're looking at
+    // intersections of e.g. a grid and guide line, then we'll have to determine 
+    // once again whether we're within snapping range. In this case we will set
+    // the snapping range to e.g. min(guide_sens, grid_sens)
+    
+    // search for the closest snapped point
+    Inkscape::SnappedPoint closestPoint;
+    if (getClosestSP(sc.points, closestPoint)) {
+        sp_list.push_back(std::make_pair(closestPoint, NR_HUGE));
+    } 
+    
+    // search for the closest snapped line segment
+    Inkscape::SnappedLineSegment closestLineSegment;
+    if (getClosestSLS(sc.lines, closestLineSegment)) {    
+        sp_list.push_back(std::make_pair(Inkscape::SnappedPoint(closestLineSegment), NR_HUGE));
+    }
+    
+    if (_intersectionLS) {
+           // search for the closest snapped intersection of line segments
+           Inkscape::SnappedPoint closestLineSegmentIntersection;
+           if (getClosestIntersectionSLS(sc.lines, closestLineSegmentIntersection)) {
+               sp_list.push_back(std::make_pair(closestLineSegmentIntersection, NR_HUGE));
+           }
+    }    
+
+    // search for the closest snapped grid line
+    Inkscape::SnappedLine closestGridLine;
+    if (getClosestSL(sc.grid_lines, closestGridLine)) {    
+        sp_list.push_back(std::make_pair(Inkscape::SnappedPoint(closestGridLine), NR_HUGE));
+    }
+    
+    // search for the closest snapped guide line
+    Inkscape::SnappedLine closestGuideLine;
+    if (getClosestSL(sc.guide_lines, closestGuideLine)) {
+        sp_list.push_back(std::make_pair(Inkscape::SnappedPoint(closestGuideLine), NR_HUGE));
+    }
+    
+    // search for the closest snapped intersection of grid lines
+    Inkscape::SnappedPoint closestGridPoint;
+    if (getClosestIntersectionSL(sc.grid_lines, closestGridPoint)) {
+        sp_list.push_back(std::make_pair(closestGridPoint, NR_HUGE));
+    }
+    
+    // search for the closest snapped intersection of guide lines
+    Inkscape::SnappedPoint closestGuidePoint;
+    if (getClosestIntersectionSL(sc.guide_lines, closestGuidePoint)) {
+        sp_list.push_back(std::make_pair(closestGuidePoint, NR_HUGE));
+    }
+    
+    // search for the closest snapped intersection of grid with guide lines
+    if (_intersectionGG) {
+           Inkscape::SnappedPoint closestGridGuidePoint;
+           if (getClosestIntersectionSL(sc.grid_lines, sc.guide_lines, closestGridGuidePoint)) {
+               sp_list.push_back(std::make_pair(closestGridGuidePoint, std::min(guide_sens, grid_sens)));
+           }
+    }
+    
+    // now let's see which snapped point gets a thumbs up
+    Inkscape::SnappedPoint bestPoint(p, NR_HUGE);
+    for (std::list<std::pair<Inkscape::SnappedPoint, NR::Coord> >::const_iterator i = sp_list.begin(); i != sp_list.end(); i++) {
+         // first find out if this snapped point is within snapping range
+         if ((*i).first.getDistance() <= (*i).second) {
+             // if it's the first point
+             bool c1 = (i == sp_list.begin());  
+             // or, if it's closer
+             bool c2 = (*i).first.getDistance() < bestPoint.getDistance(); 
+             // or, if it's just as close but at an intersection
+             bool c3 = ((*i).first.getDistance() == bestPoint.getDistance()) && (*i).first.getAtIntersection(); 
+             // then prefer this point over the previous one
+             if (c1 || c2 || c3) {
+                 bestPoint = (*i).first;
+             }
+         }
+     }
+     return bestPoint;         
+}
+
 /*
   Local Variables:
   mode:c++