void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &/*bbox_to_snap*/, ConstraintLine const &c, std::vector<SPItem const *> const */*it*/) const { if (_snap_enabled == false || _snapmanager->snapprefs.getSnapFrom(p.getSourceType()) == false) { return; } /* Get the lines that we will try to snap to */ const LineList lines = _getSnapLines(p.getPoint()); for (LineList::const_iterator i = lines.begin(); i != lines.end(); i++) { if (Geom::L2(c.getDirection()) > 0) { // Can't do a constrained snap without a constraint // constraint line Geom::Point const point_on_line = c.hasPoint() ? c.getPoint() : p.getPoint(); Geom::Line line1(point_on_line, point_on_line + c.getDirection()); // grid/guide line Geom::Point const p1 = i->second; // point at guide/grid line Geom::Point const p2 = p1 + Geom::rot90(i->first); // 2nd point at guide/grid line Geom::Line line2(p1, p2); Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default try { inters = Geom::intersection(line1, line2); } catch (Geom::InfiniteSolutions e) { // We're probably dealing with parallel lines, so snapping doesn't make any sense here continue; // jump to the next iterator in the for-loop } if (inters) { Geom::Point t = line1.pointAt((*inters).ta); const Geom::Coord dist = Geom::L2(t - p.getPoint()); 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 _addSnappedPoint(sc, t, dist, p.getSourceType(), p.getSourceNum(), true); // For any line that's within range, we will also look at it's "point on line" p1. For guides // this point coincides with its origin; for grids this is of no use, but we cannot // discern between grids and guides here Geom::Coord const dist_p1 = Geom::L2(p1 - p.getPoint()); if (dist_p1 < getSnapperTolerance()) { _addSnappedLinesOrigin(sc, p1, dist_p1, p.getSourceType(), p.getSourceNum(), true); // Only relevant for guides; grids don't have an origin per line // Therefore _addSnappedLinesOrigin() will only be implemented for guides } } } } } }
void Inkscape::LineSnapper::freeSnap(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, Geom::Point const &p, bool const &/*f*/, Geom::OptRect const &/*bbox_to_snap*/, std::vector<SPItem const *> const */*it*/, std::vector<Geom::Point> */*unselected_nodes*/) const { if (_snap_enabled == false || _snapmanager->snapprefs.getSnapFrom(t) == false) { return; } /* Get the lines that we will try to snap to */ const LineList lines = _getSnapLines(p); // std::cout << "snap point " << p << " to: " << std::endl; for (LineList::const_iterator i = lines.begin(); i != lines.end(); i++) { Geom::Point const p1 = i->second; // point at guide/grid line Geom::Point const p2 = p1 + Geom::rot90(i->first); // 2nd point at guide/grid line // std::cout << " line through " << i->second << " with normal " << i->first; g_assert(i->first != Geom::Point(0,0)); // we cannot project on an linesegment of zero length Geom::Point const p_proj = project_on_linesegment(p, p1, p2); Geom::Coord const dist = Geom::L2(p_proj - p); //Store any line that's within snapping range if (dist < getSnapperTolerance()) { _addSnappedLine(sc, p_proj, dist, i->first, i->second); // std::cout << " -> distance = " << dist; } // std::cout << std::endl; } }
void Inkscape::ObjectSnapper::_snapTranslatingGuide(IntermSnapResults &isr, Geom::Point const &p, Geom::Point const &guide_normal) const { // Iterate through all nodes, find out which one is the closest to this guide, and snap to it! _collectNodes(SNAPSOURCE_GUIDE, true); if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION, SNAPTARGET_BBOX_EDGE, SNAPTARGET_PAGE_BORDER, SNAPTARGET_TEXT_BASELINE)) { _collectPaths(p, SNAPSOURCE_GUIDE, true); _snapPaths(isr, SnapCandidatePoint(p, SNAPSOURCE_GUIDE), NULL, NULL); } SnappedPoint s; Geom::Coord tol = getSnapperTolerance(); for (std::vector<SnapCandidatePoint>::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); ++k) { Geom::Point target_pt = (*k).getPoint(); // Project each node (*k) on the guide line (running through point p) Geom::Point p_proj = Geom::projection(target_pt, Geom::Line(p, p + Geom::rot90(guide_normal))); Geom::Coord dist = Geom::L2(target_pt - p_proj); // distance from node to the guide Geom::Coord dist2 = Geom::L2(p - p_proj); // distance from projection of node on the guide, to the mouse location if ((dist < tol && dist2 < tol) || getSnapperAlwaysSnap()) { s = SnappedPoint(target_pt, SNAPSOURCE_GUIDE, 0, (*k).getTargetType(), dist, tol, getSnapperAlwaysSnap(), false, true, (*k).getTargetBBox()); isr.points.push_back(s); } } }
void Inkscape::LineSnapper::freeSnap(SnappedConstraints &sc, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &/*bbox_to_snap*/, std::vector<SPItem const *> const */*it*/, std::vector<Inkscape::SnapCandidatePoint> */*unselected_nodes*/) const { if (!(_snap_enabled && _snapmanager->snapprefs.getSnapFrom(p.getSourceType())) ) { return; } /* Get the lines that we will try to snap to */ const LineList lines = _getSnapLines(p.getPoint()); for (LineList::const_iterator i = lines.begin(); i != lines.end(); i++) { Geom::Point const p1 = i->second; // point at guide/grid line Geom::Point const p2 = p1 + Geom::rot90(i->first); // 2nd point at guide/grid line // std::cout << " line through " << i->second << " with normal " << i->first; g_assert(i->first != Geom::Point(0,0)); // we cannot project on an linesegment of zero length Geom::Point const p_proj = Geom::projection(p.getPoint(), Geom::Line(p1, p2)); Geom::Coord const dist = Geom::L2(p_proj - p.getPoint()); //Store any line that's within snapping range if (dist < getSnapperTolerance()) { _addSnappedLine(sc, p_proj, dist, p.getSourceType(), p.getSourceNum(), i->first, i->second); // For any line that's within range, we will also look at it's "point on line" p1. For guides // this point coincides with its origin; for grids this is of no use, but we cannot // discern between grids and guides here Geom::Coord const dist_p1 = Geom::L2(p1 - p.getPoint()); if (dist_p1 < getSnapperTolerance()) { _addSnappedLinesOrigin(sc, p1, dist_p1, p.getSourceType(), p.getSourceNum(), false); // Only relevant for guides; grids don't have an origin per line // Therefore _addSnappedLinesOrigin() will only be implemented for guides } // std::cout << " -> distance = " << dist; } // std::cout << std::endl; } }
void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, Geom::Point const &p, bool const &/*f*/, Geom::OptRect const &/*bbox_to_snap*/, ConstraintLine const &c, std::vector<SPItem const *> const */*it*/) const { if (_snap_enabled == false || _snapmanager->snapprefs.getSnapFrom(t) == false) { return; } /* 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++) { if (Geom::L2(c.getDirection()) > 0) { // Can't do a constrained snap without a constraint /* Normal to the line we're trying to snap along */ Geom::Point const n(Geom::rot90(Geom::unit_vector(c.getDirection()))); Geom::Point const point_on_line = c.hasPoint() ? c.getPoint() : p; /* Constant term of the line we're trying to snap along */ Geom::Coord const q0 = dot(n, point_on_line); /* Constant term of the grid or guide line */ Geom::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, q0, i->first, q1, t_2geom); Geom::Point t(t_2geom); if (k == Geom::intersects) { const Geom::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, 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 } } } } }
void Inkscape::ObjectSnapper::_snapNodes(IntermSnapResults &isr, SnapCandidatePoint const &p, std::vector<SnapCandidatePoint> *unselected_nodes, SnapConstraint const &c, Geom::Point const &p_proj_on_constraint) const { // Iterate through all nodes, find out which one is the closest to p, and snap to it! _collectNodes(p.getSourceType(), p.getSourceNum() <= 0); if (unselected_nodes != NULL && unselected_nodes->size() > 0) { g_assert(_points_to_snap_to != NULL); _points_to_snap_to->insert(_points_to_snap_to->end(), unselected_nodes->begin(), unselected_nodes->end()); } SnappedPoint s; bool success = false; bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping(); for (std::vector<SnapCandidatePoint>::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); ++k) { if (_allowSourceToSnapToTarget(p.getSourceType(), (*k).getTargetType(), strict_snapping)) { Geom::Point target_pt = (*k).getPoint(); Geom::Coord dist = Geom::infinity(); if (!c.isUndefined()) { // We're snapping to nodes along a constraint only, so find out if this node // is at the constraint, while allowing for a small margin if (Geom::L2(target_pt - c.projection(target_pt)) > 1e-9) { // The distance from the target point to its projection on the constraint // is too large, so this point is not on the constraint. Skip it! continue; } dist = Geom::L2(target_pt - p_proj_on_constraint); } else { // Free (unconstrained) snapping dist = Geom::L2(target_pt - p.getPoint()); } if (dist < getSnapperTolerance() && dist < s.getSnapDistance()) { s = SnappedPoint(target_pt, p.getSourceType(), p.getSourceNum(), (*k).getTargetType(), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true, (*k).getTargetBBox()); success = true; } } } if (success) { isr.points.push_back(s); } }
void Inkscape::GuideSnapper::_addSnappedPoint(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const { SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GUIDE, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true); isr.points.push_back(dummy); }
void Inkscape::GuideSnapper::_addSnappedLine(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const &normal_to_line, Geom::Point const &point_on_line) const { SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, source_num, Inkscape::SNAPTARGET_GUIDE, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); isr.guide_lines.push_back(dummy); }
void Inkscape::LineSnapper::constrainedSnap(IntermSnapResults &isr, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &/*bbox_to_snap*/, SnapConstraint const &c, std::vector<SPItem const *> const */*it*/, std::vector<SnapCandidatePoint> */*unselected_nodes*/) const { if (_snap_enabled == false || _snapmanager->snapprefs.isSourceSnappable(p.getSourceType()) == false) { return; } // project the mouse pointer onto the constraint. Only the projected point will be considered for snapping Geom::Point pp = c.projection(p.getPoint()); /* Get the lines that we will try to snap to */ const LineList lines = _getSnapLines(pp); for (LineList::const_iterator i = lines.begin(); i != lines.end(); ++i) { Geom::Point const point_on_line = c.hasPoint() ? c.getPoint() : pp; Geom::Line gridguide_line(i->second, i->second + Geom::rot90(i->first)); if (c.isCircular()) { // Find the intersections between the line and the circular constraint // First, project the origin of the circle onto the line Geom::Point const origin = c.getPoint(); Geom::Point const p_proj = Geom::projection(origin, gridguide_line); Geom::Coord dist = Geom::L2(p_proj - origin); // distance from circle origin to constraint line Geom::Coord radius = c.getRadius(); if (dist == radius) { // Only one point of intersection; _addSnappedPoint(isr, p_proj, Geom::L2(pp - p_proj), p.getSourceType(), p.getSourceNum(), true); } else if (dist < radius) { // Two points of intersection, symmetrical with respect to the projected point // Calculate half the length of the linesegment between the two points of intersection Geom::Coord l = sqrt(radius*radius - dist*dist); Geom::Coord d = Geom::L2(gridguide_line.versor()); // length of versor, needed to normalize the versor if (d > 0) { Geom::Point v = l*gridguide_line.versor()/d; _addSnappedPoint(isr, p_proj + v, Geom::L2(p.getPoint() - (p_proj + v)), p.getSourceType(), p.getSourceNum(), true); _addSnappedPoint(isr, p_proj - v, Geom::L2(p.getPoint() - (p_proj - v)), p.getSourceType(), p.getSourceNum(), true); } } } else { // Find the intersections between the line and the linear constraint Geom::Line constraint_line(point_on_line, point_on_line + c.getDirection()); Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default try { inters = Geom::intersection(constraint_line, gridguide_line); } catch (Geom::InfiniteSolutions &e) { // We're probably dealing with parallel lines, so snapping doesn't make any sense here continue; // jump to the next iterator in the for-loop } if (inters) { Geom::Point t = constraint_line.pointAt((*inters).ta); const Geom::Coord dist = Geom::L2(t - p.getPoint()); 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 _addSnappedPoint(isr, t, dist, p.getSourceType(), p.getSourceNum(), true); } } } } }
void Inkscape::LineSnapper::freeSnap(IntermSnapResults &isr, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &/*bbox_to_snap*/, std::vector<SPItem const *> const */*it*/, std::vector<Inkscape::SnapCandidatePoint> */*unselected_nodes*/) const { if (!(_snap_enabled && _snapmanager->snapprefs.isSourceSnappable(p.getSourceType())) ) { return; } /* Get the lines that we will try to snap to */ const LineList lines = _getSnapLines(p.getPoint()); for (LineList::const_iterator i = lines.begin(); i != lines.end(); ++i) { Geom::Point const p1 = i->second; // point at guide/grid line Geom::Point const p2 = p1 + Geom::rot90(i->first); // 2nd point at guide/grid line // std::cout << " line through " << i->second << " with normal " << i->first; assert(i->first != Geom::Point(0,0)); // we cannot project on an linesegment of zero length Geom::Point const p_proj = Geom::projection(p.getPoint(), Geom::Line(p1, p2)); Geom::Coord const dist = Geom::L2(p_proj - p.getPoint()); //Store any line that's within snapping range if (dist < getSnapperTolerance()) { _addSnappedLine(isr, p_proj, dist, p.getSourceType(), p.getSourceNum(), i->first, i->second); // For any line that's within range, we will also look at it's "point on line" p1. For guides // this point coincides with its origin; for grids this is of no use, but we cannot // discern between grids and guides here Geom::Coord const dist_p1 = Geom::L2(p1 - p.getPoint()); if (dist_p1 < getSnapperTolerance()) { _addSnappedLinesOrigin(isr, p1, dist_p1, p.getSourceType(), p.getSourceNum(), false); // Only relevant for guides; grids don't have an origin per line // Therefore _addSnappedLinesOrigin() will only be implemented for guides } // Here we will try to snap either tangentially or perpendicularly to a grid/guide line // For this we need to know where the origin is located of the line that is currently being rotated, std::vector<std::pair<Geom::Point, bool> > const origins_and_vectors = p.getOriginsAndVectors(); // Now we will iterate over all the origins and vectors and see which of these will get use a tangential or perpendicular snap for (std::vector<std::pair<Geom::Point, bool> >::const_iterator it_origin_or_vector = origins_and_vectors.begin(); it_origin_or_vector != origins_and_vectors.end(); ++it_origin_or_vector) { if ((*it_origin_or_vector).second) { // if "second" is true then "first" is a vector, otherwise it's a point // When snapping a line with a constant vector (constant direction) to a guide or grid line, // then either all points will be perpendicular/tangential or none at all. This is not very useful continue; } //Geom::Point origin_doc = _snapmanager->getDesktop()->dt2doc((*it_origin_or_vector).first); // "first" contains a Geom::Point, denoting either a point Geom::Point origin = (*it_origin_or_vector).first; // "first" contains a Geom::Point, denoting either a point // We won't try to snap tangentially; a line being tangential to another line can be achieved by snapping both its endpoints // individually to the other line. There's no need to have an explicit tangential snap here, that would be redundant if (_snapmanager->snapprefs.getSnapPerp()) { // Find the point that leads to a perpendicular snap Geom::Point const origin_proj = Geom::projection(origin, Geom::Line(p1, p2)); Geom::Coord dist = Geom::L2(origin_proj - p.getPoint()); if (dist < getSnapperTolerance()) { _addSnappedLinePerpendicularly(isr, origin_proj, dist, p.getSourceType(), p.getSourceNum(), false); } } } // std::cout << " -> distance = " << dist; } // std::cout << std::endl; } }
void Inkscape::ObjectSnapper::_snapPathsTangPerp(bool snap_tang, bool snap_perp, IntermSnapResults &isr, SnapCandidatePoint const &p, Geom::Curve const *curve, SPDesktop const *dt) const { // Here we will try to snap either tangentially or perpendicularly to a single path; for this we need to know where the origin is located of the line that is currently being rotated, // or we need to know the vector of the guide which is currently being translated std::vector<std::pair<Geom::Point, bool> > const origins_and_vectors = p.getOriginsAndVectors(); // Now we will iterate over all the origins and vectors and see which of these will get use a tangential or perpendicular snap for (std::vector<std::pair<Geom::Point, bool> >::const_iterator it_origin_or_vector = origins_and_vectors.begin(); it_origin_or_vector != origins_and_vectors.end(); ++it_origin_or_vector) { Geom::Point origin_or_vector_doc = dt->dt2doc((*it_origin_or_vector).first); // "first" contains a Geom::Point, denoting either a point or vector if ((*it_origin_or_vector).second) { // if "second" is true then "first" is a vector, otherwise it's a point // So we have a vector, which tells us what tangential or perpendicular direction we're looking for if (curve->degreesOfFreedom() <= 2) { // A LineSegment has order one, and therefore 2 DOF // When snapping to a point of a line segment that has a specific tangential or normal vector, then either all point // along that line will be snapped to or no points at all will be snapped to. This is not very useful, so let's skip // any line segments and lets only snap to higher order curves continue; } // The vector is being treated as a point (relative to the origin), and has been translated to document coordinates accordingly // We need however to make it a vector again, because also the origin has been transformed origin_or_vector_doc -= dt->dt2doc(Geom::Point(0,0)); } Geom::Point point_dt; Geom::Coord dist; std::vector<double> ts; if (snap_tang) { // Find all points that lead to a tangential snap if ((*it_origin_or_vector).second) { // if "second" is true then "first" is a vector, otherwise it's a point ts = find_tangents_by_vector(origin_or_vector_doc, curve->toSBasis()); } else { ts = find_tangents(origin_or_vector_doc, curve->toSBasis()); } for (std::vector<double>::const_iterator t = ts.begin(); t != ts.end(); ++t) { point_dt = dt->doc2dt(curve->pointAt(*t)); dist = Geom::distance(point_dt, p.getPoint()); isr.points.push_back(SnappedPoint(point_dt, p.getSourceType(), p.getSourceNum(), SNAPTARGET_PATH_TANGENTIAL, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true)); } } if (snap_perp) { // Find all points that lead to a perpendicular snap if ((*it_origin_or_vector).second) { ts = find_normals_by_vector(origin_or_vector_doc, curve->toSBasis()); } else { ts = find_normals(origin_or_vector_doc, curve->toSBasis()); } for (std::vector<double>::const_iterator t = ts.begin(); t != ts.end(); ++t) { point_dt = dt->doc2dt(curve->pointAt(*t)); dist = Geom::distance(point_dt, p.getPoint()); isr.points.push_back(SnappedPoint(point_dt, p.getSourceType(), p.getSourceNum(), SNAPTARGET_PATH_PERPENDICULAR, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true)); } } } }
void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent, std::vector<SPItem const *> const *it, bool const &first_point, Geom::Rect const &bbox_to_snap, bool const clip_or_mask, Geom::Affine const additional_affine) const // transformation of the item being clipped / masked { SPDesktop const *dt = _snapmanager->getDesktop(); if (dt == NULL) { g_warning("desktop == NULL, so we cannot snap; please inform the developers of this bug"); // Apparently the setup() method from the SnapManager class hasn't been called before trying to snap. } if (first_point) { _candidates->clear(); } Geom::Rect bbox_to_snap_incl = bbox_to_snap; // _incl means: will include the snapper tolerance bbox_to_snap_incl.expandBy(getSnapperTolerance()); // see? for ( SPObject *o = parent->firstChild(); o; o = o->getNext() ) { g_assert(dt != NULL); if (SP_IS_ITEM(o) && !(dt->itemIsHidden(SP_ITEM(o)) && !clip_or_mask)) { // Snapping to items in a locked layer is allowed // Don't snap to hidden objects, unless they're a clipped path or a mask /* See if this item is on the ignore list */ std::vector<SPItem const *>::const_iterator i; if (it != NULL) { i = it->begin(); while (i != it->end() && *i != o) { ++i; } } if (it == NULL || i == it->end()) { SPItem *item = SP_ITEM(o); if (item) { if (!clip_or_mask) { // cannot clip or mask more than once // The current item is not a clipping path or a mask, but might // still be the subject of clipping or masking itself ; if so, then // we should also consider that path or mask for snapping to SPObject *obj = SP_OBJECT(item->clip_ref ? item->clip_ref->getObject() : NULL); if (obj && _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH_CLIP)) { _findCandidates(obj, it, false, bbox_to_snap, true, item->i2doc_affine()); } obj = SP_OBJECT(item->mask_ref ? item->mask_ref->getObject() : NULL); if (obj && _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH_MASK)) { _findCandidates(obj, it, false, bbox_to_snap, true, item->i2doc_affine()); } } } if (SP_IS_GROUP(o)) { _findCandidates(o, it, false, bbox_to_snap, clip_or_mask, additional_affine); } else { Geom::OptRect bbox_of_item; Preferences *prefs = Preferences::get(); int prefs_bbox = prefs->getBool("/tools/bounding_box", 0); // We'll only need to obtain the visual bounding box if the user preferences tell // us to, AND if we are snapping to the bounding box itself. If we're snapping to // paths only, then we can just as well use the geometric bounding box (which is faster) SPItem::BBoxType bbox_type = (!prefs_bbox && _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_CATEGORY)) ? SPItem::VISUAL_BBOX : SPItem::GEOMETRIC_BBOX; if (clip_or_mask) { // Oh oh, this will get ugly. We cannot use sp_item_i2d_affine directly because we need to // insert an additional transformation in document coordinates (code copied from sp_item_i2d_affine) bbox_of_item = item->bounds(bbox_type, item->i2doc_affine() * additional_affine * dt->doc2dt()); } else { bbox_of_item = item->desktopBounds(bbox_type); } if (bbox_of_item) { // See if the item is within range if (bbox_to_snap_incl.intersects(*bbox_of_item) || (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_ROTATION_CENTER) && bbox_to_snap_incl.contains(item->getCenter()))) { // rotation center might be outside of the bounding box // This item is within snapping range, so record it as a candidate _candidates->push_back(SnapCandidateItem(item, clip_or_mask, additional_affine)); // For debugging: print the id of the candidate to the console // SPObject *obj = (SPObject*)item; // std::cout << "Snap candidate added: " << obj->getId() << std::endl; } } } } } } }
void Inkscape::ObjectSnapper::_snapPathsConstrained(IntermSnapResults &isr, SnapCandidatePoint const &p, SnapConstraint const &c, Geom::Point const &p_proj_on_constraint) const { _collectPaths(p_proj_on_constraint, p.getSourceType(), p.getSourceNum() <= 0); // Now we can finally do the real snapping, using the paths collected above SPDesktop const *dt = _snapmanager->getDesktop(); g_assert(dt != NULL); Geom::Point direction_vector = c.getDirection(); if (!is_zero(direction_vector)) { direction_vector = Geom::unit_vector(direction_vector); } // The intersection point of the constraint line with any path, must lie within two points on the // SnapConstraint: p_min_on_cl and p_max_on_cl. The distance between those points is twice the snapping tolerance Geom::Point const p_min_on_cl = dt->dt2doc(p_proj_on_constraint - getSnapperTolerance() * direction_vector); Geom::Point const p_max_on_cl = dt->dt2doc(p_proj_on_constraint + getSnapperTolerance() * direction_vector); Geom::Coord tolerance = getSnapperTolerance(); // PS: Because the paths we're about to snap to are all expressed relative to document coordinate system, we will have // to convert the snapper coordinates from the desktop coordinates to document coordinates std::vector<Geom::Path> constraint_path; if (c.isCircular()) { Geom::Circle constraint_circle(dt->dt2doc(c.getPoint()), c.getRadius()); constraint_circle.getPath(constraint_path); } else { Geom::Path constraint_line; constraint_line.start(p_min_on_cl); constraint_line.appendNew<Geom::LineSegment>(p_max_on_cl); constraint_path.push_back(constraint_line); } // Length of constraint_path will always be one bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping(); // Find all intersections of the constrained path with the snap target candidates std::vector<Geom::Point> intersections; for (std::vector<SnapCandidatePath >::const_iterator k = _paths_to_snap_to->begin(); k != _paths_to_snap_to->end(); ++k) { if (k->path_vector && _allowSourceToSnapToTarget(p.getSourceType(), (*k).target_type, strict_snapping)) { // Do the intersection math Geom::CrossingSet cs = Geom::crossings(constraint_path, *(k->path_vector)); // Store the results as intersection points unsigned int index = 0; for (Geom::CrossingSet::const_iterator i = cs.begin(); i != cs.end(); ++i) { if (index >= constraint_path.size()) { break; } // Reconstruct and store the points of intersection for (Geom::Crossings::const_iterator m = (*i).begin(); m != (*i).end(); ++m) { intersections.push_back(constraint_path[index].pointAt((*m).ta)); } index++; } //Geom::crossings will not consider the closing segment apparently, so we'll handle that separately here //TODO: This should have been fixed in rev. #9859, which makes this workaround obsolete for(Geom::PathVector::iterator it_pv = k->path_vector->begin(); it_pv != k->path_vector->end(); ++it_pv) { if (it_pv->closed()) { // Get the closing linesegment and convert it to a path Geom::Path cls; cls.close(false); cls.append(it_pv->back_closed()); // Intersect that closing path with the constrained path Geom::Crossings cs = Geom::crossings(constraint_path.front(), cls); // Reconstruct and store the points of intersection index = 0; // assuming the constraint path vector has only one path for (Geom::Crossings::const_iterator m = cs.begin(); m != cs.end(); ++m) { intersections.push_back(constraint_path[index].pointAt((*m).ta)); } } } // Convert the collected points of intersection to snapped points for (std::vector<Geom::Point>::iterator p_inters = intersections.begin(); p_inters != intersections.end(); ++p_inters) { // Convert to desktop coordinates (*p_inters) = dt->doc2dt(*p_inters); // Construct a snapped point Geom::Coord dist = Geom::L2(p.getPoint() - *p_inters); SnappedPoint s = SnappedPoint(*p_inters, p.getSourceType(), p.getSourceNum(), k->target_type, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true, k->target_bbox);; // Store the snapped point if (dist <= tolerance) { // If the intersection is within snapping range, then we might snap to it isr.points.push_back(s); } } } } }
void Inkscape::ObjectSnapper::_snapPaths(IntermSnapResults &isr, SnapCandidatePoint const &p, std::vector<SnapCandidatePoint> *unselected_nodes, SPPath const *selected_path) const { _collectPaths(p.getPoint(), p.getSourceType(), p.getSourceNum() <= 0); // Now we can finally do the real snapping, using the paths collected above SPDesktop const *dt = _snapmanager->getDesktop(); g_assert(dt != NULL); Geom::Point const p_doc = dt->dt2doc(p.getPoint()); bool const node_tool_active = _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION) && selected_path != NULL; if (p.getSourceNum() <= 0) { /* findCandidates() is used for snapping to both paths and nodes. It ignores the path that is * currently being edited, because that path requires special care: when snapping to nodes * only the unselected nodes of that path should be considered, and these will be passed on separately. * This path must not be ignored however when snapping to the paths, so we add it here * manually when applicable. * */ if (node_tool_active) { // TODO fix the function to be const correct: SPCurve *curve = curve_for_item(const_cast<SPPath*>(selected_path)); if (curve) { Geom::PathVector *pathv = pathvector_for_curve(const_cast<SPPath*>(selected_path), curve, true, true, Geom::identity(), Geom::identity()); // We will get our own copy of the path, which must be freed at some point _paths_to_snap_to->push_back(SnapCandidatePath(pathv, SNAPTARGET_PATH, Geom::OptRect(), true)); curve->unref(); } } } int num_path = 0; // _paths_to_snap_to contains multiple path_vectors, each containing multiple paths. // num_path will count the paths, and will not be zeroed for each path_vector. It will // continue counting bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping(); bool snap_perp = _snapmanager->snapprefs.getSnapPerp(); bool snap_tang = _snapmanager->snapprefs.getSnapTang(); //dt->snapindicator->remove_debugging_points(); for (std::vector<SnapCandidatePath >::const_iterator it_p = _paths_to_snap_to->begin(); it_p != _paths_to_snap_to->end(); ++it_p) { if (_allowSourceToSnapToTarget(p.getSourceType(), (*it_p).target_type, strict_snapping)) { bool const being_edited = node_tool_active && (*it_p).currently_being_edited; //if true then this pathvector it_pv is currently being edited in the node tool for(Geom::PathVector::iterator it_pv = (it_p->path_vector)->begin(); it_pv != (it_p->path_vector)->end(); ++it_pv) { // Find a nearest point for each curve within this path // n curves will return n time values with 0 <= t <= 1 std::vector<double> anp = (*it_pv).nearestPointPerCurve(p_doc); //std::cout << "#nearest points = " << anp.size() << " | p = " << p.getPoint() << std::endl; // Now we will examine each of the nearest points, and determine whether it's within snapping range and if we should snap to it std::vector<double>::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); //dt->snapindicator->set_new_debugging_point(sp_doc*dt->doc2dt()); 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 * and not to the pieces that are being dragged around. This way we avoid * self-snapping. For this we check whether the nodes at both ends of the current * piece are unselected; if they are then this piece must be stationary */ g_assert(unselected_nodes != NULL); Geom::Point start_pt = dt->doc2dt(curve->pointAt(0)); Geom::Point end_pt = dt->doc2dt(curve->pointAt(1)); c1 = isUnselectedNode(start_pt, unselected_nodes); c2 = isUnselectedNode(end_pt, unselected_nodes); /* Unfortunately, this might yield false positives for coincident nodes. Inkscape might therefore mistakenly * snap to path segments that are not stationary. There are at least two possible ways to overcome this: * - Linking the individual nodes of the SPPath we have here, to the nodes of the NodePath::SubPath class as being * used in sp_nodepath_selected_nodes_move. This class has a member variable called "selected". For this the nodes * should be in the exact same order for both classes, so we can index them * - Replacing the SPPath being used here by the NodePath::SubPath class; but how? */ } Geom::Point const sp_dt = dt->doc2dt(sp_doc); if (!being_edited || (c1 && c2)) { Geom::Coord dist = Geom::distance(sp_doc, p_doc); // std::cout << " dist -> " << dist << std::endl; if (dist < getSnapperTolerance()) { // Add the curve we have snapped to Geom::Point sp_tangent_dt = Geom::Point(0,0); if (p.getSourceType() == Inkscape::SNAPSOURCE_GUIDE_ORIGIN) { // We currently only use the tangent when snapping guides, so only in this case we will // actually calculate the tangent to avoid wasting CPU cycles Geom::Point sp_tangent_doc = curve->unitTangentAt(*np); sp_tangent_dt = dt->doc2dt(sp_tangent_doc) - dt->doc2dt(Geom::Point(0,0)); } isr.curves.push_back(SnappedCurve(sp_dt, sp_tangent_dt, num_path, index, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, curve, p.getSourceType(), p.getSourceNum(), it_p->target_type, it_p->target_bbox)); if (snap_tang || snap_perp) { // For each curve that's within snapping range, we will now also search for tangential and perpendicular snaps _snapPathsTangPerp(snap_tang, snap_perp, isr, p, curve, dt); } } } } num_path++; } // End of: for (Geom::PathVector::iterator ....) } } }