From 0721326f41e78698936345823f415b2b643eb2ff Mon Sep 17 00:00:00 2001 From: dvlierop2 Date: Thu, 3 Jan 2008 21:37:41 +0000 Subject: [PATCH] 1) fix snapping while scaling 2) fix constrained snapping 3) improve snapping logic (again), or more specifically: better obey "always snap" 4) refactoring to reduce risk of bugs: renaming variables and methods for better readability, adding class members instead of using std::pairs, etc. --- src/desktop.cpp | 6 +- src/display/canvas-axonomgrid.cpp | 2 +- src/display/canvas-grid.cpp | 2 +- src/guide-snapper.cpp | 2 +- src/line-snapper.cpp | 55 +++++----- src/object-snapper.cpp | 31 +++--- src/snap.cpp | 177 ++++++++++++++++++------------ src/snapped-line.cpp | 99 ++++++++++++----- src/snapped-line.h | 6 +- src/snapped-point.cpp | 41 ++++++- src/snapped-point.h | 19 +++- src/snapper.cpp | 23 ++-- src/snapper.h | 16 ++- 13 files changed, 304 insertions(+), 175 deletions(-) diff --git a/src/desktop.cpp b/src/desktop.cpp index feb3f5cb0..e52d2b50c 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -1535,15 +1535,15 @@ _update_snap_distances (SPDesktop *desktop) //tell all grid snappers for ( GSList const *l = nv.grids; l != NULL; l = l->next) { Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data; - grid->snapper->setDistance(sp_convert_distance_full(nv.gridtolerance, + grid->snapper->setSnapperTolerance(sp_convert_distance_full(nv.gridtolerance, *nv.gridtoleranceunit, px)); } - nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance, + nv.snap_manager.guide.setSnapperTolerance(sp_convert_distance_full(nv.guidetolerance, *nv.guidetoleranceunit, px)); - nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance, + nv.snap_manager.object.setSnapperTolerance(sp_convert_distance_full(nv.objecttolerance, *nv.objecttoleranceunit, px)); } diff --git a/src/display/canvas-axonomgrid.cpp b/src/display/canvas-axonomgrid.cpp index 3d9375d2d..c64887d1c 100644 --- a/src/display/canvas-axonomgrid.cpp +++ b/src/display/canvas-axonomgrid.cpp @@ -709,7 +709,7 @@ CanvasAxonomGridSnapper::_getSnapLines(NR::Point const &p) const void CanvasAxonomGridSnapper::_addSnappedLine(SnappedConstraints &sc, NR::Point const snapped_point, NR::Coord const snapped_distance, NR::Point const normal_to_line, NR::Point const point_on_line) const { - SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, normal_to_line, point_on_line); + SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); sc.grid_lines.push_back(dummy); } diff --git a/src/display/canvas-grid.cpp b/src/display/canvas-grid.cpp index 15a6452d3..20856709c 100644 --- a/src/display/canvas-grid.cpp +++ b/src/display/canvas-grid.cpp @@ -921,7 +921,7 @@ CanvasXYGridSnapper::_getSnapLines(NR::Point const &p) const void CanvasXYGridSnapper::_addSnappedLine(SnappedConstraints &sc, NR::Point const snapped_point, NR::Coord const snapped_distance, NR::Point const normal_to_line, NR::Point const point_on_line) const { - SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, normal_to_line, point_on_line); + SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); sc.grid_lines.push_back(dummy); } diff --git a/src/guide-snapper.cpp b/src/guide-snapper.cpp index fb16382d2..41e2cdb07 100644 --- a/src/guide-snapper.cpp +++ b/src/guide-snapper.cpp @@ -48,7 +48,7 @@ bool Inkscape::GuideSnapper::ThisSnapperMightSnap() const void Inkscape::GuideSnapper::_addSnappedLine(SnappedConstraints &sc, NR::Point const snapped_point, NR::Coord const snapped_distance, NR::Point const normal_to_line, NR::Point const point_on_line) const { - SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, normal_to_line, point_on_line); + SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); sc.guide_lines.push_back(dummy); } diff --git a/src/line-snapper.cpp b/src/line-snapper.cpp index 7fc6a32a4..a84a80296 100644 --- a/src/line-snapper.cpp +++ b/src/line-snapper.cpp @@ -30,8 +30,6 @@ void Inkscape::LineSnapper::_doFreeSnap(SnappedConstraints &sc, std::vector &points_to_snap, std::list const &it) const { - Inkscape::SnappedPoint s = SnappedPoint(p, NR_HUGE); - /* Get the lines that we will try to snap to */ const LineList lines = _getSnapLines(p); @@ -46,7 +44,7 @@ void Inkscape::LineSnapper::_doFreeSnap(SnappedConstraints &sc, NR::Point const p_proj = project_on_linesegment(p, p1, p2); NR::Coord const dist = NR::L2(p_proj - p); //Store any line that's within snapping range - if (dist < getDistance()) { + if (dist < getSnapperTolerance()) { _addSnappedLine(sc, p_proj, dist, i->first, i->second); // std::cout << " -> distance = " << dist; } @@ -63,36 +61,35 @@ void Inkscape::LineSnapper::_doConstrainedSnap(SnappedConstraints &sc, std::list const &/*it*/) const { - Inkscape::SnappedPoint s = SnappedPoint(p, NR_HUGE); - /* Get the lines that we will try to snap to */ const LineList lines = _getSnapLines(p); for (LineList::const_iterator i = lines.begin(); i != lines.end(); i++) { - - /* Normal to the line we're trying to snap along */ - NR::Point const n(NR::rot90(NR::unit_vector(c.getDirection()))); - - NR::Point const point_on_line = c.hasPoint() ? c.getPoint() : p; - - /* Constant term of the line we're trying to snap along */ - NR::Coord const q0 = dot(n, point_on_line); - /* Constant term of the grid or guide line */ - NR::Coord const q1 = dot(i->first, i->second); - - /* Try to intersect this line with the target line */ - Geom::Point t_2geom(NR_HUGE, NR_HUGE); - Geom::IntersectorKind const k = Geom::line_intersection(n.to_2geom(), q0, i->first.to_2geom(), q1, t_2geom); - NR::Point t(t_2geom); - - if (k == Geom::intersects) { - const NR::Coord dist = L2(t - p); - if (dist < getDistance()) { - // When doing a constrained snap, we're already at an intersection. - // This snappoint is therefore fully constrained, so there's no need - // to look for additional intersections; just return the snapped point - // and forget about the line - sc.points.push_back(SnappedPoint(t, dist)); + if (NR::L2(c.getDirection()) > 0) { // Can't do a constrained snap without a constraint + /* Normal to the line we're trying to snap along */ + NR::Point const n(NR::rot90(NR::unit_vector(c.getDirection()))); + + NR::Point const point_on_line = c.hasPoint() ? c.getPoint() : p; + + /* Constant term of the line we're trying to snap along */ + NR::Coord const q0 = dot(n, point_on_line); + /* Constant term of the grid or guide line */ + NR::Coord const q1 = dot(i->first, i->second); + + /* Try to intersect this line with the target line */ + Geom::Point t_2geom(NR_HUGE, NR_HUGE); + Geom::IntersectorKind const k = Geom::line_intersection(n.to_2geom(), q0, i->first.to_2geom(), q1, t_2geom); + NR::Point t(t_2geom); + + if (k == Geom::intersects) { + const NR::Coord dist = L2(t - p); + if (dist < getSnapperTolerance()) { + // When doing a constrained snap, we're already at an intersection. + // This snappoint is therefore fully constrained, so there's no need + // to look for additional intersections; just return the snapped point + // and forget about the line + sc.points.push_back(SnappedPoint(t, dist, getSnapperTolerance(), getSnapperAlwaysSnap())); + } } } } diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index c03884ffd..9ffdc764c 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -98,9 +98,9 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* r, for (std::vector::const_iterator i = points_to_snap.begin(); i != points_to_snap.end(); i++) { NR::Point b_min = b->min(); NR::Point b_max = b->max(); - double d = getDistance(); - bool withinX = ((*i)[NR::X] >= b_min[NR::X] - d) && ((*i)[NR::X] <= b_max[NR::X] + d); - bool withinY = ((*i)[NR::Y] >= b_min[NR::Y] - d) && ((*i)[NR::Y] <= b_max[NR::Y] + d); + NR::Coord t = getSnapperTolerance(); + bool withinX = ((*i)[NR::X] >= b_min[NR::X] - t) && ((*i)[NR::X] <= b_max[NR::X] + t); + bool withinY = ((*i)[NR::Y] >= b_min[NR::Y] - t) && ((*i)[NR::Y] <= b_max[NR::Y] + t); bool c1 = snap_dim == GUIDE_TRANSL_SNAP_X && withinX; bool c2 = snap_dim == GUIDE_TRANSL_SNAP_Y && withinY; bool c3 = snap_dim == TRANSL_SNAP_XY && withinX && withinY; @@ -191,8 +191,8 @@ void Inkscape::ObjectSnapper::_snapNodes(SnappedConstraints &sc, for (std::vector::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) { NR::Coord dist = NR::L2(*k - p); - if (dist < getDistance() && dist < s.getDistance()) { - s = SnappedPoint(*k, dist); + if (dist < getSnapperTolerance() && dist < s.getDistance()) { + s = SnappedPoint(*k, dist, getSnapperTolerance(), getSnapperAlwaysSnap()); success = true; } } @@ -217,8 +217,8 @@ void Inkscape::ObjectSnapper::_snapTranslatingGuideToNodes(SnappedConstraints &s // Project each node (*k) on the guide line (running through point p) NR::Point p_proj = project_on_linesegment(*k, p, p + NR::rot90(guide_normal)); NR::Coord dist = NR::L2(*k - p_proj); - if (dist < getDistance() && dist < s.getDistance()) { - s = SnappedPoint(*k, dist); + if (dist < getSnapperTolerance() && dist < s.getDistance()) { + s = SnappedPoint(*k, dist, getSnapperTolerance(), getSnapperAlwaysSnap()); success = true; } } @@ -351,7 +351,7 @@ void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc, NR::Point const o_dt = desktop->doc2dt(o_it); NR::Coord const dist = NR::L2(o_dt - p); - if (dist < getDistance()) { + if (dist < getSnapperTolerance()) { // if we snap to a straight line segment (within a path), then return this line segment if ((*k)->IsLineSegment(o->piece)) { NR::Point start_point; @@ -360,11 +360,11 @@ void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc, (*k)->PointAt(o->piece, 1, end_point); start_point = desktop->doc2dt(start_point); end_point = desktop->doc2dt(end_point); - sc.lines.push_back(Inkscape::SnappedLineSegment(o_dt, dist, start_point, end_point)); + sc.lines.push_back(Inkscape::SnappedLineSegment(o_dt, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), start_point, end_point)); } else { // for segments other than straight lines of a path, we'll return just the closest snapped point if (dist < s.getDistance()) { - s = SnappedPoint(o_dt, dist); + s = SnappedPoint(o_dt, dist, getSnapperTolerance(), getSnapperAlwaysSnap()); success = true; } } @@ -404,10 +404,10 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, // The intersection point of the constraint line with any path, // must lie within two points on the constraintline: p_min_on_cl and p_max_on_cl - // The distance between those points is twice the max. snapping distance + // The distance between those points is twice the snapping tolerance NR::Point const p_proj_on_cl = project_on_linesegment(p, p1_on_cl, p2_on_cl); - NR::Point const p_min_on_cl = desktop->dt2doc(p_proj_on_cl - getDistance() * direction_vector); - NR::Point const p_max_on_cl = desktop->dt2doc(p_proj_on_cl + getDistance() * direction_vector); + NR::Point const p_min_on_cl = desktop->dt2doc(p_proj_on_cl - getSnapperTolerance() * direction_vector); + NR::Point const p_max_on_cl = desktop->dt2doc(p_proj_on_cl + getSnapperTolerance() * direction_vector); Geom::Path cl; cl.start(p_min_on_cl.to_2geom()); @@ -416,7 +416,7 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, for (std::vector::const_iterator k = _bpaths_to_snap_to->begin(); k != _bpaths_to_snap_to->end(); k++) { if (*k) { // convert a Path object (see src/livarot/Path.h) to a 2geom's path object (see 2geom/path.h) - // TODO (Diederik) Only do this once for the first point, needs some storage of pointers in a member variable + // TODO: (Diederik) Only do this once for the first point, needs some storage of pointers in a member variable std::vector path_2geom = BPath_to_2GeomPath(*k); for (std::vector::const_iterator l = path_2geom.begin(); l != path_2geom.end(); l++) { @@ -428,7 +428,8 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, // When it's within snapping range, then return it // (within snapping range == between p_min_on_cl and p_max_on_cl == 0 < tb < 1) if ((*m).tb >= 0 && (*m).tb <= 1 ) { - SnappedPoint s(desktop->doc2dt(p_inters), NR::L2(p_proj_on_cl - p_inters)); + NR::Coord dist = NR::L2(desktop->dt2doc(p_proj_on_cl) - p_inters); + SnappedPoint s(desktop->doc2dt(p_inters), dist, getSnapperTolerance(), getSnapperAlwaysSnap()); sc.points.push_back(s); } } diff --git a/src/snap.cpp b/src/snap.cpp index 62449f37d..a6e165d50 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -209,7 +209,7 @@ Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t, std::list const &it) const { if (!SomeSnapperMightSnap()) { - return Inkscape::SnappedPoint(p, NR_HUGE); + return Inkscape::SnappedPoint(p, NR_HUGE, 0, false); } SnappedConstraints sc; @@ -271,7 +271,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType std::list const &it) const { if (!SomeSnapperMightSnap()) { - return Inkscape::SnappedPoint(p, NR_HUGE); + return Inkscape::SnappedPoint(p, NR_HUGE, 0, false); } SnappedConstraints sc; @@ -290,7 +290,7 @@ Inkscape::SnappedPoint SnapManager::guideSnap(NR::Point const &p, // This method is used to snap a guide to nodes, while dragging the guide around if (!(object.ThisSnapperMightSnap() && _snap_enabled_globally)) { - return Inkscape::SnappedPoint(p, NR_HUGE); + return Inkscape::SnappedPoint(p, NR_HUGE, 0, false); } SnappedConstraints sc; @@ -386,7 +386,9 @@ std::pair SnapManager::_snapTransformed( */ NR::Coord best_metric = NR_HUGE; NR::Coord best_second_metric = NR_HUGE; + NR::Point best_scale_metric(NR_HUGE, NR_HUGE); bool best_at_intersection = false; + bool best_always_snap = false; std::vector::const_iterator j = transformed_points.begin(); @@ -416,6 +418,7 @@ std::pair SnapManager::_snapTransformed( NR::Point result; NR::Coord metric = NR_HUGE; NR::Coord second_metric = NR_HUGE; + NR::Point scale_metric(NR_HUGE, NR_HUGE); if (snapped.getDistance() < NR_HUGE) { /* We snapped. Find the transformation that describes where the snapped point has @@ -438,48 +441,38 @@ std::pair SnapManager::_snapTransformed( break; case SCALE: { - NR::Point const a = (snapped.getPoint() - origin); - NR::Point const b = (*i - origin); - result = transformation; - if (fabs(b[NR::X]) > 1e-6 && fabs(b[NR::Y]) > 1e-6) { - // This is the default scaling that results after snapping - result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]); - } else { - // If this point *i is horizontally or vertically aligned with - // the origin of the scaling, then it will scale purely in X or Y - // We can therefore only calculate the scaling in this direction - // and the scaling factor for the other direction should remain - // untouched (unless scaling is uniform ofcourse) - for (int index = 0; index < 2; index++) { - if (fabs(b[index]) > 1e-6) { - result[index] = a[index] / b[index]; - if (uniform) { - result[1-index] = result[index]; - } + NR::Point const a = (snapped.getPoint() - origin); // vector to snapped point + NR::Point const b = (*i - origin); // vector to original point + result = NR::Point(NR_HUGE, NR_HUGE); + // If this point *i is horizontally or vertically aligned with + // the origin of the scaling, then it will scale purely in X or Y + // We can therefore only calculate the scaling in this direction + // and the scaling factor for the other direction should remain + // untouched (unless scaling is uniform ofcourse) + for (int index = 0; index < 2; index++) { + if (fabs(b[index]) > 1e-6) { // if SCALING CAN occur in this direction + if (fabs(fabs(a[index]/b[index]) - fabs(transformation[index])) > 1e-12) { // if SNAPPING DID occur in this direction + result[index] = a[index] / b[index]; // then calculate it! } + // we might leave result[1-index] = NR_HUGE + // if scaling didn't occur in the other direction } } - if (fabs(b[NR::X]) <= 1e-6 && fabs(b[NR::Y]) <= 1e-6) { - metric = NR_HUGE; - } else { - // Compare the resulting scaling with the desired scaling - metric = std::abs(NR::L2(result) - NR::L2(transformation)); - } + // Compare the resulting scaling with the desired scaling + scale_metric = result - transformation; // One or both of its components might be NR_HUGE break; } case STRETCH: - { - for (int a = 0; a < 2; a++) { - if (uniform || a == dim) { - result[a] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]); + for (int index = 0; index < 2; index++) { + if (uniform || index == dim) { + result[index] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]); } else { - result[a] = 1; + result[index] = 1; } } metric = std::abs(result[dim] - transformation[dim]); break; - } case SKEW: result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]); metric = std::abs(result[dim] - transformation[dim]); @@ -489,24 +482,63 @@ std::pair SnapManager::_snapTransformed( } /* Note it if it's the best so far */ - bool const c1 = metric < best_metric; - bool const c2 = metric == best_metric && snapped.getAtIntersection() == true && best_at_intersection == false; - bool const c3a = metric == best_metric && snapped.getAtIntersection() == true && best_at_intersection == true; - bool const c3b = second_metric < best_second_metric; - - if (c1 || c2 || c3a && c3b) { - best_transformation = result; - best_metric = metric; - best_second_metric = second_metric; - best_at_intersection = snapped.getAtIntersection(); - //std::cout << "SEL "; - } //else { std::cout << " ";} + if (transformation_type == SCALE) { + for (int index = 0; index < 2; index++) { + if (fabs(scale_metric[index]) < fabs(best_scale_metric[index])) { + best_transformation[index] = result[index]; + best_scale_metric[index] = fabs(scale_metric[index]); + //std::cout << "SEL "; + } //else { std::cout << " ";} + } + if (uniform) { + if (best_scale_metric[0] < best_scale_metric[1]) { + best_transformation[1] = best_transformation[0]; + best_scale_metric[1] = best_scale_metric[0]; + } else { + best_transformation[0] = best_transformation[1]; + best_scale_metric[0] = best_scale_metric[1]; + } + } + best_metric = std::min(best_scale_metric[0], best_scale_metric[1]); + //std::cout << "P_orig = " << (*i) << " | scale_metric = " << scale_metric << " | distance = " << snapped.getDistance() << " | P_snap = " << snapped.getPoint() << std::endl; + } else { + bool const c1 = metric < best_metric; + bool const c2 = metric == best_metric && snapped.getAtIntersection() == true && best_at_intersection == false; + bool const c3a = metric == best_metric && snapped.getAtIntersection() == true && best_at_intersection == true; + bool const c3b = second_metric < best_second_metric; + bool const c4 = snapped.getAlwaysSnap() == true && best_always_snap == false; + bool const c4n = snapped.getAlwaysSnap() == false && best_always_snap == true; + + if ((c1 || c2 || (c3a && c3b) || c4) && !c4n) { + best_transformation = result; + best_metric = metric; + best_second_metric = second_metric; + best_at_intersection = snapped.getAtIntersection(); + best_always_snap = snapped.getAlwaysSnap(); + //std::cout << "SEL "; + } //else { std::cout << " ";} + //std::cout << "P_orig = " << (*i) << " | metric = " << metric << " | distance = " << snapped.getDistance() << " | second metric = " << second_metric << " | P_snap = " << snapped.getPoint() << std::endl; + } } - //std::cout << "P_orig = " << (*i) << " | metric = " << metric << " | distance = " << snapped.getDistance() << " | second metric = " << second_metric << " | P_snap = " << snapped.getPoint() << std::endl; + j++; } + if (transformation_type == SCALE) { + // When scaling, don't ever exit with one of scaling components set to NR_HUGE + if (best_transformation == NR::Point(NR_HUGE, NR_HUGE)) { + best_transformation == transformation; // return the original (i.e. un-snapped) transformation + } else { + // Still one of the transformation components could be NR_HUGE + for (int index = 0; index < 2; index++) { + if (best_transformation[index] == NR_HUGE) { + best_transformation[index] == uniform ? best_transformation[1-index] : transformation[index]; + } + } + } + } + // 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); @@ -669,54 +701,54 @@ std::pair SnapManager::freeSnapSkew(Inkscape::Snapper::PointTyp Inkscape::SnappedPoint SnapManager::findBestSnap(NR::Point const &p, SnappedConstraints &sc, bool constrained) const { - NR::Coord const guide_sens = guide.getDistance(); - NR::Coord grid_sens = 0; + NR::Coord const guide_tol = guide.getSnapperTolerance(); + NR::Coord grid_tol = 0; SnapManager::SnapperList const gs = getGridSnappers(); SnapperList::const_iterator i = gs.begin(); if (i != gs.end()) { - grid_sens = (*i)->getDistance(); + grid_tol = (*i)->getSnapperTolerance(); // there's only a single tolerance, equal for all grids } - // Store all snappoints, optionally together with their specific snapping range - std::list > sp_list; + // Store all snappoints + std::list 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) + // the snapping range to e.g. min(guide_sens, grid_tol) // search for the closest snapped point Inkscape::SnappedPoint closestPoint; if (getClosestSP(sc.points, closestPoint)) { - sp_list.push_back(std::make_pair(closestPoint, NR_HUGE)); + sp_list.push_back(closestPoint); } // 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)); + sp_list.push_back(Inkscape::SnappedPoint(closestLineSegment)); } 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)); + sp_list.push_back(closestLineSegmentIntersection); } } // 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)); + sp_list.push_back(Inkscape::SnappedPoint(closestGridLine)); } // 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)); + sp_list.push_back(Inkscape::SnappedPoint(closestGuideLine)); } // When freely snapping to a grid/guide/path, only one degree of freedom is eliminated @@ -729,44 +761,49 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(NR::Point const &p, SnappedCons // 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)); + sp_list.push_back(closestGridPoint); } // 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)); + sp_list.push_back(closestGuidePoint); } // 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))); + sp_list.push_back(closestGridGuidePoint); } } } // now let's see which snapped point gets a thumbs up - Inkscape::SnappedPoint bestPoint(p, NR_HUGE); - for (std::list >::const_iterator i = sp_list.begin(); i != sp_list.end(); i++) { + Inkscape::SnappedPoint bestSnappedPoint = Inkscape::SnappedPoint(p, NR_HUGE, 0, false); + for (std::list::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 ((*i).getDistance() <= (*i).getTolerance()) { // 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 then consider the second distance + bool c2 = (*i).getDistance() < bestSnappedPoint.getDistance(); + // or, if it's for a snapper with "always snap" turned on, and the previous wasn't + bool c3 = (*i).getAlwaysSnap() && !bestSnappedPoint.getAlwaysSnap(); + // But in no case fall back from a snapper with "always snap" on to one with "always snap" off + bool c3n = !(*i).getAlwaysSnap() && bestSnappedPoint.getAlwaysSnap(); + // or, if it's just as close then consider the second distance // (which is only relevant for points at an intersection) - bool c3a = ((*i).first.getDistance() == bestPoint.getDistance()); - bool c3b = (*i).first.getSecondDistance() < bestPoint.getSecondDistance(); + bool c4a = ((*i).getDistance() == bestSnappedPoint.getDistance()); + bool c4b = (*i).getSecondDistance() < bestSnappedPoint.getSecondDistance(); // then prefer this point over the previous one - if (c1 || c2 || c3a && c3b) { - bestPoint = (*i).first; + if ((c1 || c2 || c3 || (c4a && c4b)) && !c3n) { + bestSnappedPoint = *i; } } } - return bestPoint; + + return bestSnappedPoint; } /* diff --git a/src/snapped-line.cpp b/src/snapped-line.cpp index 67fec627a..b6a5c2f9f 100644 --- a/src/snapped-line.cpp +++ b/src/snapped-line.cpp @@ -12,23 +12,31 @@ #include <2geom/geom.h> #include "libnr/nr-values.h" -Inkscape::SnappedLineSegment::SnappedLineSegment(NR::Point snapped_point, NR::Coord snapped_distance, NR::Point start_point_of_line, NR::Point end_point_of_line) +Inkscape::SnappedLineSegment::SnappedLineSegment(NR::Point snapped_point, NR::Coord snapped_distance, NR::Coord snapped_tolerance, bool always_snap, NR::Point start_point_of_line, NR::Point end_point_of_line) : _start_point_of_line(start_point_of_line), _end_point_of_line(end_point_of_line) { - _distance = snapped_distance; _point = snapped_point; + _distance = snapped_distance; + _tolerance = snapped_tolerance; + _always_snap = always_snap; _at_intersection = false; _second_distance = NR_HUGE; + _second_tolerance = 0; + _second_always_snap = false; } Inkscape::SnappedLineSegment::SnappedLineSegment() { _start_point_of_line = NR::Point(0,0); _end_point_of_line = NR::Point(0,0); - _distance = NR_HUGE; _point = NR::Point(0,0); + _distance = NR_HUGE; + _tolerance = 0; + _always_snap = false; _at_intersection = false; _second_distance = NR_HUGE; + _second_tolerance = 0; + _second_always_snap = false; } @@ -39,31 +47,45 @@ Inkscape::SnappedLineSegment::~SnappedLineSegment() Inkscape::SnappedPoint Inkscape::SnappedLineSegment::intersect(SnappedLineSegment const &line) const { Geom::Point intersection_2geom(NR_HUGE, NR_HUGE); - NR::Coord distance = NR_HUGE; - NR::Coord second_distance = NR_HUGE; - Geom::IntersectorKind result = segment_intersect(_start_point_of_line.to_2geom(), _end_point_of_line.to_2geom(), line._start_point_of_line.to_2geom(), line._end_point_of_line.to_2geom(), intersection_2geom); NR::Point intersection(intersection_2geom); if (result == Geom::intersects) { - /* The relevant snapped distance is the distance to the closest snapped line, not the - distance to the intersection. See the comment in Inkscape::SnappedLine::intersect + /* If a snapper has been told to "always snap", then this one should be preferred + * over the other, if that other one has not been told so. (The preferred snapper + * will be labelled "primary" below) + */ + bool const c1 = this->getAlwaysSnap() && !line.getAlwaysSnap(); //do not use _tolerance directly! + /* If neither or both have been told to "always snap", then cast a vote based on + * the snapped distance. For this we should consider the distance to the snapped + * line, not the distance to the intersection. + * See the comment in Inkscape::SnappedLine::intersect */ - distance = std::min(_distance, line.getDistance()); - second_distance = std::max(_distance, line.getDistance()); + bool const c2 = _distance < line.getDistance(); + bool const use_this_as_primary = c1 || c2; + Inkscape::SnappedLineSegment const *primarySLS = use_this_as_primary ? this : &line; + Inkscape::SnappedLineSegment const *secondarySLS = use_this_as_primary ? &line : this; + return SnappedPoint(intersection, primarySLS->getDistance(), primarySLS->getTolerance(), primarySLS->getAlwaysSnap(), true, + secondarySLS->getDistance(), secondarySLS->getTolerance(), secondarySLS->getAlwaysSnap()); } - return SnappedPoint(intersection, distance, result == Geom::intersects, second_distance); + + // No intersection + return SnappedPoint(intersection, NR_HUGE, 0, false, false, NR_HUGE, 0, false); }; -Inkscape::SnappedLine::SnappedLine(NR::Point snapped_point, NR::Coord snapped_distance, NR::Point normal_to_line, NR::Point point_on_line) +Inkscape::SnappedLine::SnappedLine(NR::Point snapped_point, NR::Coord snapped_distance, NR::Coord snapped_tolerance, bool always_snap, NR::Point normal_to_line, NR::Point point_on_line) : _normal_to_line(normal_to_line), _point_on_line(point_on_line) { _distance = snapped_distance; + _tolerance = snapped_tolerance; + _always_snap = always_snap; _second_distance = NR_HUGE; + _second_tolerance = 0; + _second_always_snap = false; _point = snapped_point; _at_intersection = false; } @@ -73,7 +95,11 @@ Inkscape::SnappedLine::SnappedLine() _normal_to_line = NR::Point(0,0); _point_on_line = NR::Point(0,0); _distance = NR_HUGE; + _tolerance = 0; + _always_snap = false; _second_distance = NR_HUGE; + _second_tolerance = 0; + _second_always_snap = false; _point = NR::Point(0,0); _at_intersection = false; } @@ -84,31 +110,44 @@ Inkscape::SnappedLine::~SnappedLine() Inkscape::SnappedPoint Inkscape::SnappedLine::intersect(SnappedLine const &line) const { - // Calculate the intersection of to lines, which are both within snapping range + // Calculate the intersection of two lines, which are both within snapping range + // One could be a grid line, whereas the other could be a guide line // The point of intersection should be considered for snapping, but might be outside the snapping range Geom::Point intersection_2geom(NR_HUGE, NR_HUGE); - NR::Coord distance = NR_HUGE; - NR::Coord second_distance = NR_HUGE; - - Geom::IntersectorKind result = Geom::line_intersection(getNormal().to_2geom(), getConstTerm(), + Geom::IntersectorKind result = Geom::line_intersection(getNormal().to_2geom(), getConstTerm(), line.getNormal().to_2geom(), line.getConstTerm(), intersection_2geom); NR::Point intersection(intersection_2geom); if (result == Geom::intersects) { - /* The relevant snapped distance is the distance to the closest snapped line, not the - distance to the intersection. For example, when 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 grid line. We will be snapping - to the closest snapped point however, so if we ever want to snap to the intersection - then the distance to it should at least be equal to the other distance, not greater - than it, as that would rule the intersection out - */ - distance = std::min(_distance, line.getDistance()); - second_distance = std::max(_distance, line.getDistance()); - } - - return SnappedPoint(intersection, distance, result == Geom::intersects, second_distance); + /* If a snapper has been told to "always snap", then this one should be preferred + * over the other, if that other one has not been told so. (The preferred snapper + * will be labelled "primary" below) + */ + bool const c1 = this->getAlwaysSnap() && !line.getAlwaysSnap(); + /* If neither or both have been told to "always snap", then cast a vote based on + * the snapped distance. For this we should consider the distance to the snapped + * line, not the distance to the intersection. + * + * The relevant snapped distance is the distance to the closest snapped line, not the + * distance to the intersection. For example, when 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 grid line. We will be snapping + * to the closest snapped point however, so if we ever want to snap to the intersection + * then the distance to it should at least be equal to the other distance, not greater + * than it, as that would rule the intersection out when comparing it with regular snappoint, + * as the latter will always be closer + */ + bool const c2 = _distance < line.getDistance(); + bool const use_this_as_primary = c1 || c2; + Inkscape::SnappedLine const *primarySL = use_this_as_primary ? this : &line; + Inkscape::SnappedLine const *secondarySL = use_this_as_primary ? &line : this; + return SnappedPoint(intersection, primarySL->getDistance(), primarySL->getTolerance(), primarySL->getAlwaysSnap(), true, + secondarySL->getDistance(), secondarySL->getTolerance(), secondarySL->getAlwaysSnap()); + } + + // No intersection + return SnappedPoint(intersection, NR_HUGE, 0, false, false, NR_HUGE, 0, false); } // search for the closest snapped line segment diff --git a/src/snapped-line.h b/src/snapped-line.h index be2a792b7..3616058b0 100644 --- a/src/snapped-line.h +++ b/src/snapped-line.h @@ -26,7 +26,7 @@ class SnappedLineSegment : public SnappedPoint { public: SnappedLineSegment(); - SnappedLineSegment(NR::Point snapped_point, NR::Coord snapped_distance, NR::Point start_point_of_line, NR::Point end_point_of_line); + SnappedLineSegment(NR::Point snapped_point, NR::Coord snapped_distance, NR::Coord snapped_tolerance, bool always_snap, NR::Point start_point_of_line, NR::Point end_point_of_line); ~SnappedLineSegment(); Inkscape::SnappedPoint intersect(SnappedLineSegment const &line) const; //intersect with another SnappedLineSegment @@ -41,13 +41,13 @@ class SnappedLine : public SnappedPoint { public: SnappedLine(); - SnappedLine(NR::Point snapped_point, NR::Coord snapped_distance, NR::Point normal_to_line, NR::Point point_on_line); + SnappedLine(NR::Point snapped_point, NR::Coord snapped_distance, NR::Coord snapped_tolerance, bool always_snap, NR::Point normal_to_line, NR::Point point_on_line); ~SnappedLine(); Inkscape::SnappedPoint intersect(SnappedLine const &line) const; //intersect with another SnappedLine // This line is described by this equation: // a*x + b*y = c <-> nx*px + ny+py = c <-> n.p = c NR::Point getNormal() const {return _normal_to_line;} // n = (nx, ny) - NR::Point getPointOnLine() const {return _point_on_line;} // p = (px, py) + NR::Point getPointOnLine() const {return _point_on_line;} // p = (px, py) NR::Coord getConstTerm() const {return dot(_normal_to_line, _point_on_line);} // c = n.p = nx*px + ny*py; private: diff --git a/src/snapped-point.cpp b/src/snapped-point.cpp index d97abda1d..8a0aea3c7 100644 --- a/src/snapped-point.cpp +++ b/src/snapped-point.cpp @@ -11,17 +11,32 @@ #include "snapped-point.h" -Inkscape::SnappedPoint::SnappedPoint(NR::Point p, NR::Coord d, bool at_intersection, NR::Coord d2) - : _distance(d), _point(p), _at_intersection(at_intersection), _second_distance(d2) +// overloaded constructor +Inkscape::SnappedPoint::SnappedPoint(NR::Point p, NR::Coord d, NR::Coord t, bool a) + : _point(p), _distance(d), _tolerance(t), _always_snap(a) +{ + _at_intersection = false; + _second_distance = NR_HUGE; + _second_tolerance = 0; + _second_always_snap = false; +} + +Inkscape::SnappedPoint::SnappedPoint(NR::Point p, NR::Coord d, NR::Coord t, bool a, bool at_intersection, NR::Coord d2, NR::Coord t2, bool a2) + : _point(p), _distance(d), _tolerance(t), _always_snap(a), _at_intersection(at_intersection), + _second_distance(d2), _second_tolerance(t2), _second_always_snap(a2) { } Inkscape::SnappedPoint::SnappedPoint() { - _distance = NR_HUGE; _point = NR::Point(0,0); + _distance = NR_HUGE; + _tolerance = 0; + _always_snap = false; _at_intersection = false; _second_distance = NR_HUGE; + _second_tolerance = 0; + _second_always_snap = false; } @@ -35,11 +50,31 @@ NR::Coord Inkscape::SnappedPoint::getDistance() const return _distance; } +NR::Coord Inkscape::SnappedPoint::getTolerance() const +{ + return _tolerance; +} + +bool Inkscape::SnappedPoint::getAlwaysSnap() const +{ + return _always_snap; +} + NR::Coord Inkscape::SnappedPoint::getSecondDistance() const { return _second_distance; } +NR::Coord Inkscape::SnappedPoint::getSecondTolerance() const +{ + return _second_tolerance; +} + +bool Inkscape::SnappedPoint::getSecondAlwaysSnap() const +{ + return _second_always_snap; +} + NR::Point Inkscape::SnappedPoint::getPoint() const { diff --git a/src/snapped-point.h b/src/snapped-point.h index e4b08a302..f0584812d 100644 --- a/src/snapped-point.h +++ b/src/snapped-point.h @@ -26,11 +26,16 @@ class SnappedPoint { public: SnappedPoint(); - SnappedPoint(::NR::Point p, ::NR::Coord d, bool at_intersection = false, NR::Coord d2 = NR_HUGE); + SnappedPoint(NR::Point p, NR::Coord d, NR::Coord t, bool a, bool at_intersection, NR::Coord d2, NR::Coord t2, bool a2); + SnappedPoint(NR::Point p, NR::Coord d, NR::Coord t, bool a); ~SnappedPoint(); NR::Coord getDistance() const; + NR::Coord getTolerance() const; + bool getAlwaysSnap() const; NR::Coord getSecondDistance() const; + NR::Coord getSecondTolerance() const; + bool getSecondAlwaysSnap() const; NR::Point getPoint() const; bool getAtIntersection() const {return _at_intersection;} @@ -42,12 +47,18 @@ protected: an intersection of e.g. two lines, then this is the distance to the closest line */ NR::Coord _distance; + /* The snapping tolerance in screen pixels (depends on zoom)*/ + NR::Coord _tolerance; + /* If true then "Always snap" is on */ + bool _always_snap; /* If the snapped point is at an intersection of e.g. two lines, then this is the distance to the fartest line */ - NR::Coord _second_distance; - - + NR::Coord _second_distance; + /* The snapping tolerance in screen pixels (depends on zoom)*/ + NR::Coord _second_tolerance; + /* If true then "Always snap" is on */ + bool _second_always_snap; }; } diff --git a/src/snapper.cpp b/src/snapper.cpp index 1daa1d151..efdc6bcbe 100644 --- a/src/snapper.cpp +++ b/src/snapper.cpp @@ -20,9 +20,9 @@ Inkscape::Snapper::PointType const Inkscape::Snapper::SNAPPOINT_GUIDE = 0x4; /** * Construct new Snapper for named view. * \param nv Named view. - * \param d Snap distance. + * \param d Snap tolerance. */ -Inkscape::Snapper::Snapper(SPNamedView const *nv, NR::Coord const d) : _named_view(nv), _snap_enabled(true), _distance(d) +Inkscape::Snapper::Snapper(SPNamedView const *nv, NR::Coord const t) : _named_view(nv), _snap_enabled(true), _snapper_tolerance(t) { g_assert(_named_view != NULL); g_assert(SP_IS_NAMEDVIEW(_named_view)); @@ -31,20 +31,25 @@ Inkscape::Snapper::Snapper(SPNamedView const *nv, NR::Coord const d) : _named_vi } /** - * Set snap distance. - * \param d New snap distance (desktop coordinates) + * Set snap tolerance. + * \param d New snap tolerance (desktop coordinates) */ -void Inkscape::Snapper::setDistance(NR::Coord const d) +void Inkscape::Snapper::setSnapperTolerance(NR::Coord const d) { - _distance = d; + _snapper_tolerance = d; } /** - * \return Snap distance (desktop coordinates); depends on current zoom so that it's always the same in screen pixels + * \return Snap tolerance (desktop coordinates); depends on current zoom so that it's always the same in screen pixels */ -NR::Coord Inkscape::Snapper::getDistance() const +NR::Coord Inkscape::Snapper::getSnapperTolerance() const { - return _distance / SP_ACTIVE_DESKTOP->current_zoom(); + return _snapper_tolerance / SP_ACTIVE_DESKTOP->current_zoom(); +} + +bool Inkscape::Snapper::getSnapperAlwaysSnap() const +{ + return _snapper_tolerance == 10000; //TODO: Replace this threshold of 10000 by a constant; see also tolerance-slider.cpp } /** diff --git a/src/snapper.h b/src/snapper.h index cf2533f22..8aeea0a26 100644 --- a/src/snapper.h +++ b/src/snapper.h @@ -7,6 +7,7 @@ * * Authors: * Carl Hetherington + * Diederik van Lierop * * Released under GNU GPL, read the file 'COPYING' for more information. */ @@ -47,11 +48,12 @@ public: static const PointType SNAPPOINT_GUIDE; void setSnapFrom(PointType t, bool s); - void setDistance(::NR::Coord d); - bool getSnapFrom(PointType t) const; - ::NR::Coord getDistance() const; - + + void setSnapperTolerance(NR::Coord t); + NR::Coord getSnapperTolerance() const; //returns the tolerance of the snapper in screen pixels (i.e. independent of zoom) + bool getSnapperAlwaysSnap() const; //if true, then the snapper will always snap, regardless of its tolerance + /** * \return true if this Snapper will snap at least one kind of point. */ @@ -121,6 +123,10 @@ protected: bool _snap_enabled; ///< true if this snapper is enabled, otherwise false private: + NR::Coord _snapper_tolerance; ///< snap tolerance in desktop coordinates + // must be private to enforce the usage of getTolerance(), which retrieves + // the tolerance in screen pixels (making it zoom independent) + /** * Try to snap a point to whatever this snapper is interested in. Any @@ -155,8 +161,6 @@ private: std::vector &points_to_snap, ConstraintLine const &c, std::list const &it) const = 0; - - NR::Coord _distance; ///< snap distance (desktop coordinates) }; } -- 2.30.2