From e781423d621c888bfbcfab74512d797385606aa1 Mon Sep 17 00:00:00 2001 From: dvlierop2 Date: Sun, 21 Sep 2008 20:18:04 +0000 Subject: [PATCH] - refactor snapping code, removing code duplication - fix snapping to page corners --- src/gradient-drag.cpp | 4 +- src/line-snapper.cpp | 2 +- src/object-snapper.cpp | 18 +-- src/snap.cpp | 110 +++++++--------- src/snapped-curve.cpp | 292 +++++++++++++++++++++-------------------- src/snapped-curve.h | 2 +- src/snapped-line.cpp | 8 +- src/snapped-point.cpp | 28 +++- src/snapped-point.h | 16 ++- 9 files changed, 248 insertions(+), 232 deletions(-) diff --git a/src/gradient-drag.cpp b/src/gradient-drag.cpp index 1caaf1d11..9432d4b5a 100644 --- a/src/gradient-drag.cpp +++ b/src/gradient-drag.cpp @@ -593,7 +593,7 @@ gr_knot_moved_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpoi dist = fabs(p[NR::Y] - dragger->parent->hor_levels[i]); if (dist < snap_dist) { p[NR::Y] = dragger->parent->hor_levels[i]; - s = Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_GRADIENT, dist, snap_dist, false); + s = Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_GRADIENT, dist, snap_dist, false, false); was_snapped = true; sp_knot_moveto (knot, p); } @@ -602,7 +602,7 @@ gr_knot_moved_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpoi dist = fabs(p[NR::X] - dragger->parent->vert_levels[i]); if (dist < snap_dist) { p[NR::X] = dragger->parent->vert_levels[i]; - s = Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_GRADIENT, dist, snap_dist, false); + s = Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_GRADIENT, dist, snap_dist, false, false); was_snapped = true; sp_knot_moveto (knot, p); } diff --git a/src/line-snapper.cpp b/src/line-snapper.cpp index 5453c1cf2..b2934c2e3 100644 --- a/src/line-snapper.cpp +++ b/src/line-snapper.cpp @@ -97,7 +97,7 @@ void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, // 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, Inkscape::SNAPTARGET_UNDEFINED, dist, getSnapperTolerance(), getSnapperAlwaysSnap())); + sc.points.push_back(SnappedPoint(t, Inkscape::SNAPTARGET_UNDEFINED, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true)); // The type of the snap target is yet undefined, as we cannot tell whether // we're snapping to grid or the guide lines; must be set by on a higher level } diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index 41866b41c..13fba8060 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -98,7 +98,7 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent, for (SPObject* o = sp_object_first_child(parent); o != NULL; o = SP_OBJECT_NEXT(o)) { g_assert(_snapmanager->getDesktop() != NULL); - if (SP_IS_ITEM(o) && !SP_ITEM(o)->isLocked() && !(_snapmanager->getDesktop()->itemIsHidden(SP_ITEM(o)) && !clip_or_mask)) { + if (SP_IS_ITEM(o) && !SP_ITEM(o)->isLocked() && !(_snapmanager->getDesktop()->itemIsHidden(SP_ITEM(o)) && !clip_or_mask)) { // Don't snap to locked items, and // don't snap to hidden objects, unless they're a clipped path or a mask /* See if this item is on the ignore list */ @@ -249,7 +249,7 @@ void Inkscape::ObjectSnapper::_snapNodes(SnappedConstraints &sc, for (std::vector::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) { Geom::Coord dist = Geom::L2(*k - p); if (dist < getSnapperTolerance() && dist < s.getDistance()) { - s = SnappedPoint(*k, SNAPTARGET_NODE, dist, getSnapperTolerance(), getSnapperAlwaysSnap()); + s = SnappedPoint(*k, SNAPTARGET_NODE, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true); success = true; } } @@ -278,7 +278,7 @@ void Inkscape::ObjectSnapper::_snapTranslatingGuideToNodes(SnappedConstraints &s Geom::Coord dist = Geom::L2(*k - 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()) && dist < s.getDistance()) { - s = SnappedPoint(*k, SNAPTARGET_NODE, dist, tol, getSnapperAlwaysSnap()); + s = SnappedPoint(*k, SNAPTARGET_NODE, dist, tol, getSnapperAlwaysSnap(), true); success = true; } } @@ -438,10 +438,10 @@ void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc, std::vector::const_iterator np = anp.begin(); unsigned int index = 0; for (; np != anp.end(); np++, index++) { - Geom::Curve const *curve = &((*it_pv).at_index(index)); - Geom::Point const sp_doc = curve->pointAt(*np); - - bool c1 = true; + Geom::Curve const *curve = &((*it_pv).at_index(index)); + Geom::Point const sp_doc = curve->pointAt(*np); + + bool c1 = true; bool c2 = true; if (being_edited) { /* If the path is being edited, then we should only snap though to stationary pieces of the path @@ -460,7 +460,7 @@ void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc, if (!being_edited || (c1 && c2)) { Geom::Coord const dist = Geom::distance(sp_doc, p_doc); if (dist < getSnapperTolerance()) { - sc.curves.push_back(Inkscape::SnappedCurve(from_2geom(sp_dt), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), curve)); + sc.curves.push_back(Inkscape::SnappedCurve(from_2geom(sp_dt), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, curve)); } } } @@ -536,7 +536,7 @@ 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 < ta < 1) Geom::Coord dist = Geom::L2(_snapmanager->getDesktop()->dt2doc(p_proj_on_cl) - p_inters); - SnappedPoint s(_snapmanager->getDesktop()->doc2dt(p_inters), SNAPTARGET_PATH, dist, getSnapperTolerance(), getSnapperAlwaysSnap()); + SnappedPoint s(_snapmanager->getDesktop()->doc2dt(p_inters), SNAPTARGET_PATH, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true); sc.points.push_back(s); } } diff --git a/src/snap.cpp b/src/snap.cpp index c4ed536e5..394bc7f91 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -206,7 +206,7 @@ Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType point_ boost::optional const &bbox_to_snap) const { if (!SomeSnapperMightSnap()) { - return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false); + return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } std::vector *items_to_ignore; @@ -328,7 +328,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType boost::optional const &bbox_to_snap) const { if (!SomeSnapperMightSnap()) { - return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false); + return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } std::vector *items_to_ignore; @@ -463,8 +463,6 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( /* The current best metric for the best transformation; lower is better, NR_HUGE ** means that we haven't snapped anything. */ - Geom::Coord best_metric = NR_HUGE; - Geom::Coord best_second_metric = NR_HUGE; Geom::Point best_scale_metric(NR_HUGE, NR_HUGE); Inkscape::SnappedPoint best_snapped_point; g_assert(best_snapped_point.getAlwaysSnap() == false); // Check initialization of snapped point @@ -503,8 +501,6 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( } Geom::Point result; - Geom::Coord metric = NR_HUGE; - Geom::Coord second_metric = NR_HUGE; Geom::Point scale_metric(NR_HUGE, NR_HUGE); if (snapped_point.getSnapped()) { @@ -526,8 +522,8 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( * distance is defined as the distance to the nearest line of the intersection, * and not to the intersection itself! */ - metric = snapped_point.getDistance(); //used to be: metric = Geom::L2(result); - second_metric = snapped_point.getSecondDistance(); + // Only for translations, the relevant metric will be the real snapped distance, + // so we don't have to do anything special here break; case SCALE: { @@ -561,18 +557,24 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( result[dim] = result[1-dim]; } } - metric = std::abs(result[dim] - transformation[dim]); + // Store the metric for this transformation as a virtual distance + snapped_point.setDistance(std::abs(result[dim] - transformation[dim])); + snapped_point.setSecondDistance(NR_HUGE); break; case SKEW: result[0] = (snapped_point.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]); // skew factor result[1] = transformation[1]; // scale factor - metric = std::abs(result[0] - transformation[0]); + // Store the metric for this transformation as a virtual distance + snapped_point.setDistance(std::abs(result[0] - transformation[0])); + snapped_point.setSecondDistance(NR_HUGE); break; default: g_assert_not_reached(); } - /* Note it if it's the best so far */ + // When scaling, we're considering the best transformation in each direction separately. We will have a metric in each + // direction, whereas for all other transformation we only a single one-dimensional metric. That's why we need to handle + // the scaling metric differently if (transformation_type == SCALE) { for (int index = 0; index < 2; index++) { if (fabs(scale_metric[index]) < fabs(best_scale_metric[index])) { @@ -594,41 +596,32 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( 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_point.getDistance() << " | P_snap = " << snapped_point.getPoint() << std::endl; - } else { - bool const c1 = metric < best_metric; - bool const c2 = metric == best_metric && snapped_point.getAtIntersection() == true && best_snapped_point.getAtIntersection() == false; - bool const c3a = metric == best_metric && snapped_point.getAtIntersection() == true && best_snapped_point.getAtIntersection() == true; - bool const c3b = second_metric < best_second_metric; - bool const c4 = snapped_point.getAlwaysSnap() == true && best_snapped_point.getAlwaysSnap() == false; - bool const c4n = snapped_point.getAlwaysSnap() == false && best_snapped_point.getAlwaysSnap() == true; - - if ((c1 || c2 || (c3a && c3b) || c4) && !c4n) { + } else { // For all transformations other than scaling + if (best_snapped_point.isOtherOneBetter(snapped_point)) { best_transformation = result; - best_metric = metric; - best_second_metric = second_metric; - best_snapped_point = snapped_point; - // std::cout << "SEL "; - } // else { std::cout << " ";} - // std::cout << "P_orig = " << (*i) << " | metric = " << metric << " | distance = " << snapped_point.getDistance() << " | second metric = " << second_metric << " | P_snap = " << snapped_point.getPoint() << std::endl; + best_snapped_point = snapped_point; + } } } j++; } + Geom::Coord best_metric; if (transformation_type == SCALE) { // When scaling, don't ever exit with one of scaling components set to NR_HUGE for (int index = 0; index < 2; index++) { if (best_transformation[index] == NR_HUGE) { if (uniform && best_transformation[1-index] < NR_HUGE) { - best_transformation[index] = best_transformation[1-index]; + best_transformation[index] = best_transformation[1-index]; } else { - best_transformation[index] = transformation[index]; + best_transformation[index] = transformation[index]; } } } + best_metric = std::min(best_scale_metric[0], best_scale_metric[1]); + } else { // For all transformations other than scaling + best_metric = best_snapped_point.getDistance(); } best_snapped_point.setTransformation(best_transformation); @@ -769,14 +762,16 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(Inkscape::Snapper::Point Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, SnappedConstraints &sc, bool constrained) const { + /* std::cout << "Type and number of snapped constraints: " << std::endl; std::cout << " Points : " << sc.points.size() << std::endl; std::cout << " Lines : " << sc.lines.size() << std::endl; std::cout << " Grid lines : " << sc.grid_lines.size()<< std::endl; std::cout << " Guide lines : " << sc.guide_lines.size()<< std::endl; + std::cout << " Curves : " << sc.curves.size()<< std::endl; */ - + // Store all snappoints std::list sp_list; @@ -793,11 +788,11 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, SnappedCo } if (_intersectionCS) { - // search for the closest snapped intersection of curves - Inkscape::SnappedPoint closestCurvesIntersection; - if (getClosestIntersectionCS(sc.curves, p, closestCurvesIntersection)) { - sp_list.push_back(closestCurvesIntersection); - } + // search for the closest snapped intersection of curves + Inkscape::SnappedPoint closestCurvesIntersection; + if (getClosestIntersectionCS(sc.curves, p, closestCurvesIntersection)) { + sp_list.push_back(closestCurvesIntersection); + } } // search for the closest snapped grid line @@ -837,38 +832,29 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, SnappedCo // 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)) { - closestGridGuidePoint.setTarget(Inkscape::SNAPTARGET_GRID_GUIDE_INTERSECTION); + Inkscape::SnappedPoint closestGridGuidePoint; + if (getClosestIntersectionSL(sc.grid_lines, sc.guide_lines, closestGridGuidePoint)) { + closestGridGuidePoint.setTarget(Inkscape::SNAPTARGET_GRID_GUIDE_INTERSECTION); sp_list.push_back(closestGridGuidePoint); - } + } } } // now let's see which snapped point gets a thumbs up - Inkscape::SnappedPoint bestSnappedPoint = Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false); + Inkscape::SnappedPoint bestSnappedPoint = Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); + // std::cout << "Finding the best snap..." << std::endl; 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 + // first find out if this snapped point is within snapping range + // std::cout << "sp = " << from_2geom((*i).getPoint()); 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).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 c4a = ((*i).getDistance() == bestSnappedPoint.getDistance()); - bool c4b = (*i).getSecondDistance() < bestSnappedPoint.getSecondDistance(); - // then prefer this point over the previous one - if ((c1 || c2 || c3 || (c4a && c4b)) && !c3n) { + // if it's the first point, or if it is closer than the best snapped point so far + if (i == sp_list.begin() || bestSnappedPoint.isOtherOneBetter(*i)) { + // then prefer this point over the previous one bestSnappedPoint = *i; } } - } - + // std::cout << std::endl; + } // Update the snap indicator, if requested if (_snapindicator) { @@ -886,7 +872,7 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, SnappedCo void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, SPItem const *item_to_ignore, std::vector *unselected_nodes) { g_assert(desktop != NULL); - _item_to_ignore = item_to_ignore; + _item_to_ignore = item_to_ignore; _items_to_ignore = NULL; _desktop = desktop; _snapindicator = snapindicator; @@ -895,8 +881,8 @@ void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, SPItem con void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, std::vector &items_to_ignore, std::vector *unselected_nodes) { - g_assert(desktop != NULL); - _item_to_ignore = NULL; + g_assert(desktop != NULL); + _item_to_ignore = NULL; _items_to_ignore = &items_to_ignore; _desktop = desktop; _snapindicator = snapindicator; @@ -905,7 +891,7 @@ void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, std::vecto SPDocument *SnapManager::getDocument() const { - return _named_view->document; + return _named_view->document; } /* diff --git a/src/snapped-curve.cpp b/src/snapped-curve.cpp index dfed84531..3a6512e5e 100644 --- a/src/snapped-curve.cpp +++ b/src/snapped-curve.cpp @@ -1,145 +1,147 @@ -/** - * \file src/snapped-curve.cpp - * \brief SnappedCurve class. - * - * Authors: - * Diederik van Lierop - * - * Released under GNU GPL, read the file 'COPYING' for more information. - */ - -#include "snapped-curve.h" -#include "libnr/nr-values.h" -#include <2geom/crossing.h> -#include <2geom/path-intersection.h> -#include - -// These two are needed for SP_ACTIVE_DESKTOP; this is a dirty hack -#include "desktop.h" -#include "inkscape.h" - -Inkscape::SnappedCurve::SnappedCurve(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, Geom::Coord const &snapped_tolerance, bool const &always_snap, Geom::Curve const *curve) -{ - _distance = snapped_distance; - _tolerance = snapped_tolerance; - _always_snap = always_snap; - _curve = curve; - _second_distance = NR_HUGE; - _second_tolerance = 0; - _second_always_snap = false; - _point = snapped_point; - _at_intersection = false; -} - -Inkscape::SnappedCurve::SnappedCurve() -{ - _distance = NR_HUGE; - _tolerance = 0; - _always_snap = false; - _curve = NULL; - _second_distance = NR_HUGE; - _second_tolerance = 0; - _second_always_snap = false; - _point = Geom::Point(0,0); - _at_intersection = false; -} - -Inkscape::SnappedCurve::~SnappedCurve() -{ -} - -Inkscape::SnappedPoint Inkscape::SnappedCurve::intersect(SnappedCurve const &curve, Geom::Point const &p) const -{ - // Calculate the intersections of two curves, which are both within snapping range, and - // return only the closest intersection - // The point of intersection should be considered for snapping, but might be outside the snapping range - // PS: We need p (the location of the mouse pointer) for find out which intersection is the - // closest, as there might be multiple intersections of two curves - Geom::Crossings cs = crossings(*(this->_curve), *(curve._curve)); - - if (cs.size() > 0) { - // There might be multiple intersections: find the closest - Geom::Coord best_dist = NR_HUGE; - Geom::Point best_p = Geom::Point(NR_HUGE, NR_HUGE); - for (std::vector::const_iterator i = cs.begin(); i != cs.end(); i++) { - Geom::Point p_ix = this->_curve->pointAt((*i).ta); - Geom::Coord dist = Geom::distance(p_ix, p); - if (dist < best_dist) { - best_dist = dist; - best_p = p_ix; - } - } - - // Now we've found the closests intersection, return it as a SnappedPoint - bool const use_this_as_primary = _distance < curve.getDistance(); - Inkscape::SnappedCurve const *primaryC = use_this_as_primary ? this : &curve; - Inkscape::SnappedCurve const *secondaryC = use_this_as_primary ? &curve : this; - // The intersection should in fact be returned in desktop coordinates, but for this - // we need a desktop: this is a dirty hack - SPDesktop const *desktop = SP_ACTIVE_DESKTOP; - best_p = desktop->dt2doc(best_p); - // TODO: Investigate whether it is possible to use document coordinates everywhere - // in the snapper code. Only the mouse position should be in desktop coordinates, I guess. - // All paths are already in document coords and we are certainly not going to change THAT. - return SnappedPoint(from_2geom(best_p), Inkscape::SNAPTARGET_PATH_INTERSECTION, primaryC->getDistance(), primaryC->getTolerance(), primaryC->getAlwaysSnap(), true, - secondaryC->getDistance(), secondaryC->getTolerance(), secondaryC->getAlwaysSnap()); - } - - // No intersection - return SnappedPoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, NR_HUGE, 0, false); -} - -// search for the closest snapped line -bool getClosestCurve(std::list const &list, Inkscape::SnappedCurve &result) -{ - bool success = false; - - for (std::list::const_iterator i = list.begin(); i != list.end(); i++) { - if ((i == list.begin()) || (*i).getDistance() < result.getDistance()) { - result = *i; - success = true; - } - } - - return success; -} - -// search for the closest intersection of two snapped curves, which are both member of the same collection -bool getClosestIntersectionCS(std::list const &list, Geom::Point const &p, Inkscape::SnappedPoint &result) -{ - bool success = false; - - for (std::list::const_iterator i = list.begin(); i != list.end(); i++) { - std::list::const_iterator j = i; - j++; - for (; j != list.end(); j++) { - Inkscape::SnappedPoint sp = (*i).intersect(*j, p); - if (sp.getAtIntersection()) { - // if it's the first point - bool const c1 = !success; - // or, if it's closer - bool const c2 = sp.getDistance() < result.getDistance(); - // or, if it's just then look at the other distance - // (only relevant for snapped points which are at an intersection - bool const c3 = (sp.getDistance() == result.getDistance()) && (sp.getSecondDistance() < result.getSecondDistance()); - // then prefer this point over the previous one - if (c1 || c2 || c3) { - result = sp; - success = true; - } - } - } - } - - return success; -} -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : +/** + * \file src/snapped-curve.cpp + * \brief SnappedCurve class. + * + * Authors: + * Diederik van Lierop + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include "snapped-curve.h" +#include "libnr/nr-values.h" +#include <2geom/crossing.h> +#include <2geom/path-intersection.h> +#include + +// These two are needed for SP_ACTIVE_DESKTOP; this is a dirty hack +#include "desktop.h" +#include "inkscape.h" + +Inkscape::SnappedCurve::SnappedCurve(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, Geom::Coord const &snapped_tolerance, bool const &always_snap, bool const &fully_constrained, Geom::Curve const *curve) +{ + _distance = snapped_distance; + _tolerance = snapped_tolerance; + _always_snap = always_snap; + _curve = curve; + _second_distance = NR_HUGE; + _second_tolerance = 0; + _second_always_snap = false; + _point = snapped_point; + _at_intersection = false; + _fully_constrained = fully_constrained; +} + +Inkscape::SnappedCurve::SnappedCurve() +{ + _distance = NR_HUGE; + _tolerance = 0; + _always_snap = false; + _curve = NULL; + _second_distance = NR_HUGE; + _second_tolerance = 0; + _second_always_snap = false; + _point = Geom::Point(0,0); + _at_intersection = false; + _fully_constrained = false; +} + +Inkscape::SnappedCurve::~SnappedCurve() +{ +} + +Inkscape::SnappedPoint Inkscape::SnappedCurve::intersect(SnappedCurve const &curve, Geom::Point const &p) const +{ + // Calculate the intersections of two curves, which are both within snapping range, and + // return only the closest intersection + // The point of intersection should be considered for snapping, but might be outside the snapping range + // PS: We need p (the location of the mouse pointer) for find out which intersection is the + // closest, as there might be multiple intersections of two curves + Geom::Crossings cs = crossings(*(this->_curve), *(curve._curve)); + + if (cs.size() > 0) { + // There might be multiple intersections: find the closest + Geom::Coord best_dist = NR_HUGE; + Geom::Point best_p = Geom::Point(NR_HUGE, NR_HUGE); + for (std::vector::const_iterator i = cs.begin(); i != cs.end(); i++) { + Geom::Point p_ix = this->_curve->pointAt((*i).ta); + Geom::Coord dist = Geom::distance(p_ix, p); + if (dist < best_dist) { + best_dist = dist; + best_p = p_ix; + } + } + + // Now we've found the closests intersection, return it as a SnappedPoint + bool const use_this_as_primary = _distance < curve.getDistance(); + Inkscape::SnappedCurve const *primaryC = use_this_as_primary ? this : &curve; + Inkscape::SnappedCurve const *secondaryC = use_this_as_primary ? &curve : this; + // The intersection should in fact be returned in desktop coordinates, but for this + // we need a desktop: this is a dirty hack + SPDesktop const *desktop = SP_ACTIVE_DESKTOP; + best_p = desktop->dt2doc(best_p); + // TODO: Investigate whether it is possible to use document coordinates everywhere + // in the snapper code. Only the mouse position should be in desktop coordinates, I guess. + // All paths are already in document coords and we are certainly not going to change THAT. + return SnappedPoint(from_2geom(best_p), Inkscape::SNAPTARGET_PATH_INTERSECTION, primaryC->getDistance(), primaryC->getTolerance(), primaryC->getAlwaysSnap(), true, true, + secondaryC->getDistance(), secondaryC->getTolerance(), secondaryC->getAlwaysSnap()); + } + + // No intersection + return SnappedPoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false, NR_HUGE, 0, false); +} + +// search for the closest snapped line +bool getClosestCurve(std::list const &list, Inkscape::SnappedCurve &result) +{ + bool success = false; + + for (std::list::const_iterator i = list.begin(); i != list.end(); i++) { + if ((i == list.begin()) || (*i).getDistance() < result.getDistance()) { + result = *i; + success = true; + } + } + + return success; +} + +// search for the closest intersection of two snapped curves, which are both member of the same collection +bool getClosestIntersectionCS(std::list const &list, Geom::Point const &p, Inkscape::SnappedPoint &result) +{ + bool success = false; + + for (std::list::const_iterator i = list.begin(); i != list.end(); i++) { + std::list::const_iterator j = i; + j++; + for (; j != list.end(); j++) { + Inkscape::SnappedPoint sp = (*i).intersect(*j, p); + if (sp.getAtIntersection()) { + // if it's the first point + bool const c1 = !success; + // or, if it's closer + bool const c2 = sp.getDistance() < result.getDistance(); + // or, if it's just then look at the other distance + // (only relevant for snapped points which are at an intersection + bool const c3 = (sp.getDistance() == result.getDistance()) && (sp.getSecondDistance() < result.getSecondDistance()); + // then prefer this point over the previous one + if (c1 || c2 || c3) { + result = sp; + success = true; + } + } + } + } + + return success; +} +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/snapped-curve.h b/src/snapped-curve.h index e6e71e648..814777b68 100644 --- a/src/snapped-curve.h +++ b/src/snapped-curve.h @@ -27,7 +27,7 @@ class SnappedCurve : public SnappedPoint { public: SnappedCurve(); - SnappedCurve(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, Geom::Coord const &snapped_tolerance, bool const &always_snap, Geom::Curve const *curve); + SnappedCurve(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, Geom::Coord const &snapped_tolerance, bool const &always_snap, bool const &fully_constrained, Geom::Curve const *curve); ~SnappedCurve(); Inkscape::SnappedPoint intersect(SnappedCurve const &curve, Geom::Point const &p) const; //intersect with another SnappedCurve diff --git a/src/snapped-line.cpp b/src/snapped-line.cpp index f35c7a81c..fc2d059dc 100644 --- a/src/snapped-line.cpp +++ b/src/snapped-line.cpp @@ -67,12 +67,12 @@ Inkscape::SnappedPoint Inkscape::SnappedLineSegment::intersect(SnappedLineSegmen 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, SNAPTARGET_PATH_INTERSECTION, primarySLS->getDistance(), primarySLS->getTolerance(), primarySLS->getAlwaysSnap(), true, + return SnappedPoint(intersection, SNAPTARGET_PATH_INTERSECTION, primarySLS->getDistance(), primarySLS->getTolerance(), primarySLS->getAlwaysSnap(), true, true, secondarySLS->getDistance(), secondarySLS->getTolerance(), secondarySLS->getAlwaysSnap()); } // No intersection - return SnappedPoint(intersection, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, NR_HUGE, 0, false); + return SnappedPoint(intersection, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false, NR_HUGE, 0, false); }; @@ -142,14 +142,14 @@ Inkscape::SnappedPoint Inkscape::SnappedLine::intersect(SnappedLine const &line) 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, Inkscape::SNAPTARGET_UNDEFINED, primarySL->getDistance(), primarySL->getTolerance(), primarySL->getAlwaysSnap(), true, + return SnappedPoint(intersection, Inkscape::SNAPTARGET_UNDEFINED, primarySL->getDistance(), primarySL->getTolerance(), primarySL->getAlwaysSnap(), true, true, secondarySL->getDistance(), secondarySL->getTolerance(), secondarySL->getAlwaysSnap()); // The type of the snap target is yet undefined, as we cannot tell whether // we're snapping to grid or the guide lines; must be set by on a higher level } // No intersection - return SnappedPoint(intersection, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, NR_HUGE, 0, false); + return SnappedPoint(intersection, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false, NR_HUGE, 0, false); } // search for the closest snapped line segment diff --git a/src/snapped-point.cpp b/src/snapped-point.cpp index 3a3595a80..4176d4a3d 100644 --- a/src/snapped-point.cpp +++ b/src/snapped-point.cpp @@ -12,18 +12,19 @@ #include "snapped-point.h" // overloaded constructor -Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a) +Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained) : _point(p), _target(target), _distance(d), _tolerance(t), _always_snap(a) { _at_intersection = false; + _fully_constrained = fully_constrained; _second_distance = NR_HUGE; _second_tolerance = 0; _second_always_snap = false; _transformation = Geom::Point(1,1); } -Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &at_intersection, Geom::Coord const &d2, Geom::Coord const &t2, bool const &a2) - : _point(p), _target(target), _at_intersection(at_intersection), _distance(d), _tolerance(t), _always_snap(a), +Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &at_intersection, bool const &fully_constrained, Geom::Coord const &d2, Geom::Coord const &t2, bool const &a2) + : _point(p), _target(target), _at_intersection(at_intersection), _fully_constrained(fully_constrained), _distance(d), _tolerance(t), _always_snap(a), _second_distance(d2), _second_tolerance(t2), _second_always_snap(a2) { _transformation = Geom::Point(1,1); @@ -104,6 +105,27 @@ bool getClosestSP(std::list &list, Inkscape::SnappedPoin return success; } +bool Inkscape::SnappedPoint::isOtherOneBetter(Inkscape::SnappedPoint const &other_one) const +{ + // If it's closer + bool c2 = other_one.getDistance() < getDistance(); + // or, if it's for a snapper with "always snap" turned on, and the previous wasn't + bool c3 = other_one.getAlwaysSnap() && !getAlwaysSnap(); + // But in no case fall back from a snapper with "always snap" on to one with "always snap" off + bool c3n = !other_one.getAlwaysSnap() && getAlwaysSnap(); + // or, if we have a fully constrained snappoint (e.g. to a node), while the previous one was only partly constrained (e.g. to a line) + bool c4 = other_one.getFullyConstrained() && !getFullyConstrained(); + // But in no case fall back; (has less priority than c3n, so it is allowed to fall back when c3 is true, see below) + bool c4n = !other_one.getFullyConstrained() && getFullyConstrained(); + // or, if it's just as close then consider the second distance + // (which is only relevant for points at an intersection) + bool c5a = (other_one.getDistance() == getDistance()); + bool c5b = other_one.getSecondDistance() < getSecondDistance(); + + // std::cout << "c2 = " << c2 << " | c3 = " << c3 << " | c3n = " << c3n << " | c4 = " << c4 << " | c4n = " << c4n << " | c5a = " << c5a << " | c5b = " << c5b; + return (c2 || c3 || c4 || (c5a && c5b)) && !c3n && (!c4n || c3); +} + /* Local Variables: mode:c++ diff --git a/src/snapped-point.h b/src/snapped-point.h index 4999a2aae..575fd0e1c 100644 --- a/src/snapped-point.h +++ b/src/snapped-point.h @@ -42,8 +42,8 @@ class SnappedPoint public: SnappedPoint(); - SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &at_intersection, Geom::Coord const &d2, Geom::Coord const &t2, bool const &a2); - SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a); + SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &at_intersection, bool const &fully_constrained, Geom::Coord const &d2, Geom::Coord const &t2, bool const &a2); + SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained); ~SnappedPoint(); Geom::Coord getDistance() const; @@ -51,11 +51,12 @@ public: Geom::Coord getTolerance() const; bool getAlwaysSnap() const; Geom::Coord getSecondDistance() const; + void setSecondDistance(Geom::Coord const d) {_second_distance = d;} Geom::Coord getSecondTolerance() const; bool getSecondAlwaysSnap() const; - /* This is the preferred method to find out which point we have snapped, - * to because it only returns a point if snapping has actually occured + /* This is the preferred method to find out which point we have snapped + * to, because it only returns a point if snapping has actually occured * (by overwriting p) */ void getPoint(Geom::Point &p) const; @@ -69,16 +70,21 @@ public: Geom::Point getPoint() const {return _point;} bool getAtIntersection() const {return _at_intersection;} + bool getFullyConstrained() const {return _fully_constrained;} bool getSnapped() const {return _distance < NR_HUGE;} Geom::Point getTransformation() const {return _transformation;} void setTransformation(Geom::Point const t) {_transformation = t;} void setTarget(SnapTargetType const target) {_target = target;} SnapTargetType getTarget() {return _target;} + bool isOtherOneBetter(SnappedPoint const &other_one) const; + protected: Geom::Point _point; // Location of the snapped point SnapTargetType _target; // Describes to what we've snapped to - bool _at_intersection; // If true, the snapped point is at an intersection + bool _at_intersection; // If true, the snapped point is at an intersection + bool _fully_constrained; // When snapping for example to a node, then the snap will be "fully constrained". + // When snapping to a line however, the snap is only partly constrained (i.e. only in one dimension) /* Distance from original point to snapped point. If the snapped point is at an intersection of e.g. two lines, then this is the distance to the closest -- 2.30.2