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::_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::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::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); } } } } }