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