From: Diederik van Lierop Date: Mon, 12 Jul 2010 05:51:13 +0000 (+0200) Subject: - Snap while rotating an object using the selector tool X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=18d56400fb8d667bff7c64b550365d94725bfe6e;p=inkscape.git - Snap while rotating an object using the selector tool - Rename the ConstraintLine class to SnapConstraint - Move some duplicated code to 2geom --- diff --git a/src/2geom/circle.cpp b/src/2geom/circle.cpp index c3cea0ae7..00b91de12 100644 --- a/src/2geom/circle.cpp +++ b/src/2geom/circle.cpp @@ -97,6 +97,23 @@ Circle::arc(Point const& initial, Point const& inner, Point const& final, return e.arc(initial, inner, final, _svg_compliant); } +void +Circle::getPath(std::vector &path_out) { + Path pb; + + D2 B; + Linear bo = Linear(0, 2 * M_PI); + + B[0] = cos(bo,4); + B[1] = sin(bo,4); + + B = B * m_ray + m_centre; + + pb.append(SBasisCurve(B)); + + path_out.push_back(pb); +} + } // end namespace Geom diff --git a/src/2geom/circle.h b/src/2geom/circle.h index 27d4fcc3f..c346b8c8f 100644 --- a/src/2geom/circle.h +++ b/src/2geom/circle.h @@ -38,7 +38,7 @@ #include <2geom/point.h> #include <2geom/exception.h> - +#include <2geom/path.h> namespace Geom { @@ -56,6 +56,11 @@ class Circle { } + Circle(Point center, double r) + : m_centre(center), m_ray(r) + { + } + Circle(double A, double B, double C, double D) { set(A, B, C, D); @@ -86,6 +91,9 @@ class Circle arc(Point const& initial, Point const& inner, Point const& final, bool _svg_compliant = true); + void + getPath(std::vector &path_out); + Point center() const { return m_centre; diff --git a/src/context-fns.cpp b/src/context-fns.cpp index 0ff7bd120..b22cd488d 100644 --- a/src/context-fns.cpp +++ b/src/context-fns.cpp @@ -132,11 +132,11 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item /* Try to snap p[0] (the opposite corner) along the constraint vector */ s[0] = m.constrainedSnap(Inkscape::SnapCandidatePoint(p[0], Inkscape::SNAPSOURCE_NODE_HANDLE), - Inkscape::Snapper::ConstraintLine(p[0] - p[1])); + Inkscape::Snapper::SnapConstraint(p[0] - p[1])); /* Try to snap p[1] (the dragged corner) along the constraint vector */ s[1] = m.constrainedSnap(Inkscape::SnapCandidatePoint(p[1], Inkscape::SNAPSOURCE_NODE_HANDLE), - Inkscape::Snapper::ConstraintLine(p[1] - p[0])); + Inkscape::Snapper::SnapConstraint(p[1] - p[0])); /* Choose the best snap and update points accordingly */ if (s[0].getSnapDistance() < s[1].getSnapDistance()) { @@ -157,7 +157,7 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item /* Our origin is the opposite corner. Snap the drag point along the constraint vector */ p[0] = center; snappoint = m.constrainedSnap(Inkscape::SnapCandidatePoint(p[1], Inkscape::SNAPSOURCE_NODE_HANDLE), - Inkscape::Snapper::ConstraintLine(p[1] - p[0])); + Inkscape::Snapper::SnapConstraint(p[1] - p[0])); if (snappoint.getSnapped()) { p[1] = snappoint.getPoint(); } diff --git a/src/display/canvas-axonomgrid.cpp b/src/display/canvas-axonomgrid.cpp index 37469fa73..d17689b06 100644 --- a/src/display/canvas-axonomgrid.cpp +++ b/src/display/canvas-axonomgrid.cpp @@ -794,9 +794,10 @@ void CanvasAxonomGridSnapper::_addSnappedLine(SnappedConstraints &sc, Geom::Poin sc.grid_lines.push_back(dummy); } -void CanvasAxonomGridSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const +void CanvasAxonomGridSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap, Geom::Coord angle) const { SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true); + dummy.setTransformation(Geom::Point(angle, angle)); // Store the rotation (in radians), needed in case of snapping while rotating sc.points.push_back(dummy); } diff --git a/src/display/canvas-axonomgrid.h b/src/display/canvas-axonomgrid.h index 58185e2e6..4a9820792 100644 --- a/src/display/canvas-axonomgrid.h +++ b/src/display/canvas-axonomgrid.h @@ -79,7 +79,7 @@ public: private: LineList _getSnapLines(Geom::Point const &p) const; void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const normal_to_line, const Geom::Point point_on_line) const; - void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const; + void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap, Geom::Coord angle) const; CanvasAxonomGrid *grid; }; diff --git a/src/display/canvas-grid.cpp b/src/display/canvas-grid.cpp index a79a6b610..fe1e92824 100644 --- a/src/display/canvas-grid.cpp +++ b/src/display/canvas-grid.cpp @@ -1030,9 +1030,10 @@ void CanvasXYGridSnapper::_addSnappedLine(SnappedConstraints &sc, Geom::Point co sc.grid_lines.push_back(dummy); } -void CanvasXYGridSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const +void CanvasXYGridSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap, Geom::Coord angle) const { SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true); + dummy.setTransformation(Geom::Point(angle, angle)); // Store the rotation (in radians), needed in case of snapping while rotating sc.points.push_back(dummy); } diff --git a/src/display/canvas-grid.h b/src/display/canvas-grid.h index a11d77d1d..0aedb02a0 100644 --- a/src/display/canvas-grid.h +++ b/src/display/canvas-grid.h @@ -167,7 +167,7 @@ public: private: LineList _getSnapLines(Geom::Point const &p) const; void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const normal_to_line, const Geom::Point point_on_line) const; - void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const; + void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap, Geom::Coord angle) const; CanvasXYGrid *grid; }; diff --git a/src/display/snap-indicator.cpp b/src/display/snap-indicator.cpp index fe5bd0371..0409e64b1 100644 --- a/src/display/snap-indicator.cpp +++ b/src/display/snap-indicator.cpp @@ -51,7 +51,6 @@ SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const &p, bool pre_snap g_assert(_desktop != NULL); if (!p.getSnapped()) { - g_warning("No snapping took place, so no snap target will be displayed"); return; // If we haven't snapped, then it is of no use to draw a snapindicator } diff --git a/src/draw-context.cpp b/src/draw-context.cpp index 3049f3a6a..a531b88d1 100644 --- a/src/draw-context.cpp +++ b/src/draw-context.cpp @@ -511,7 +511,7 @@ void spdc_endpoint_snap_rotation(SPEventContext const *const ec, Geom::Point &p, /* Snap it along best vector */ SnapManager &m = SP_EVENT_CONTEXT_DESKTOP(ec)->namedview->snap_manager; m.setup(SP_EVENT_CONTEXT_DESKTOP(ec)); - m.constrainedSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE, Inkscape::Snapper::ConstraintLine(best)); + m.constrainedSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE, Inkscape::Snapper::SnapConstraint(best)); } } } diff --git a/src/gradient-drag.cpp b/src/gradient-drag.cpp index 25309dd61..1492e9008 100644 --- a/src/gradient-drag.cpp +++ b/src/gradient-drag.cpp @@ -688,7 +688,7 @@ gr_knot_moved_handler(SPKnot *knot, Geom::Point const &ppointer, guint state, gp snap_vector = get_snap_vector (p, dr_snap, M_PI/snaps, 0); } if (snap_vector) { - Inkscape::Snapper::ConstraintLine cl(dr_snap, p + *snap_vector - dr_snap); + Inkscape::Snapper::SnapConstraint cl(dr_snap, p + *snap_vector - dr_snap); Inkscape::SnappedPoint s = m.constrainedSnap(Inkscape::SnapCandidatePoint(p + *snap_vector, Inkscape::SNAPSOURCE_OTHER_HANDLE), cl); if (s.getSnapped()) { s.setTransformation(s.getPoint() - p); @@ -838,7 +838,7 @@ gr_knot_moved_midpoint_handler(SPKnot */*knot*/, Geom::Point const &ppointer, gu } else { p = snap_vector_midpoint (p, low_lim, high_lim, 0); if (!(state & GDK_SHIFT_MASK)) { - Inkscape::Snapper::ConstraintLine cl(low_lim, high_lim - low_lim); + Inkscape::Snapper::SnapConstraint cl(low_lim, high_lim - low_lim); SPDesktop *desktop = dragger->parent->desktop; SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop); diff --git a/src/guide-snapper.cpp b/src/guide-snapper.cpp index 4f70521e0..aad4502ca 100644 --- a/src/guide-snapper.cpp +++ b/src/guide-snapper.cpp @@ -81,9 +81,10 @@ void Inkscape::GuideSnapper::_addSnappedLinesOrigin(SnappedConstraints &sc, Geom } -void Inkscape::GuideSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const +void Inkscape::GuideSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap, Geom::Coord angle) const { SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GUIDE, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true); + dummy.setTransformation(Geom::Point(angle, angle)); // Store the rotation (in radians), needed in case of snapping while rotating sc.points.push_back(dummy); } diff --git a/src/guide-snapper.h b/src/guide-snapper.h index 5de1b56a4..4e5e5d1d1 100644 --- a/src/guide-snapper.h +++ b/src/guide-snapper.h @@ -36,7 +36,7 @@ private: LineList _getSnapLines(Geom::Point const &p) const; void _addSnappedLine(SnappedConstraints &sc, 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; void _addSnappedLinesOrigin(SnappedConstraints &sc, Geom::Point const origin, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const; - void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const; + void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap, Geom::Coord angle) const; }; } diff --git a/src/knot-holder-entity.cpp b/src/knot-holder-entity.cpp index 2d0d5eb02..be61125c4 100644 --- a/src/knot-holder-entity.cpp +++ b/src/knot-holder-entity.cpp @@ -101,7 +101,7 @@ KnotHolderEntity::snap_knot_position(Geom::Point const &p) } Geom::Point -KnotHolderEntity::snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::ConstraintLine const &constraint) +KnotHolderEntity::snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::SnapConstraint const &constraint) { Geom::Matrix const i2d (sp_item_i2d_affine(item)); Geom::Point s = p * i2d; @@ -123,7 +123,7 @@ KnotHolderEntity::snap_knot_position_constrained(Geom::Point const &p, Inkscape: } else { // constrainedSnap() will first project the point p onto the constraint line and then try to snap along that line. // This way the constraint is already enforced, no need to worry about that later on - Inkscape::Snapper::ConstraintLine transformed_constraint = Inkscape::Snapper::ConstraintLine(constraint.getPoint() * i2d, (constraint.getPoint() + constraint.getDirection()) * i2d - constraint.getPoint() * i2d); + Inkscape::Snapper::SnapConstraint transformed_constraint = Inkscape::Snapper::SnapConstraint(constraint.getPoint() * i2d, (constraint.getPoint() + constraint.getDirection()) * i2d - constraint.getPoint() * i2d); m.constrainedSnapReturnByRef(s, Inkscape::SNAPSOURCE_NODE_HANDLE, transformed_constraint); } diff --git a/src/knot-holder-entity.h b/src/knot-holder-entity.h index c8fd29ddf..aba93798a 100644 --- a/src/knot-holder-entity.h +++ b/src/knot-holder-entity.h @@ -58,7 +58,7 @@ public: //private: Geom::Point snap_knot_position(Geom::Point const &p); - Geom::Point snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::ConstraintLine const &constraint); + Geom::Point snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::SnapConstraint const &constraint); SPKnot *knot; SPItem *item; diff --git a/src/line-snapper.cpp b/src/line-snapper.cpp index fc40643c8..5ceece66b 100644 --- a/src/line-snapper.cpp +++ b/src/line-snapper.cpp @@ -63,7 +63,7 @@ void Inkscape::LineSnapper::freeSnap(SnappedConstraints &sc, void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &/*bbox_to_snap*/, - ConstraintLine const &c, + SnapConstraint const &c, std::vector const */*it*/) const { @@ -75,20 +75,46 @@ void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, 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::Point const point_on_line = c.hasPoint() ? c.getPoint() : p.getPoint(); + 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::Point v_orig = c.getDirection(); // vector from the origin to the original (untransformed) point + Geom::Point v_proj = p_proj - origin; + Geom::Coord dist = Geom::L2(v_proj); // distance from circle origin to constraint line + Geom::Coord radius = c.getRadius(); + Geom::Coord radians = NR_HUGE; + if (dist == radius) { + // Only one point of intersection; + // Calculate the rotation in radians... + radians = atan2(Geom::dot(Geom::rot90(v_orig), v_proj), Geom::dot(v_orig, v_proj)); + _addSnappedPoint(sc, p_proj, Geom::L2(p.getPoint() - p_proj), p.getSourceType(), p.getSourceNum(), true, radians); + } 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; + v_proj = p_proj + v - origin; + radians = atan2(Geom::dot(Geom::rot90(v_orig), v_proj), Geom::dot(v_orig, v_proj)); + _addSnappedPoint(sc, p_proj + v, Geom::L2(p.getPoint() - (p_proj + v)), p.getSourceType(), p.getSourceNum(), true, radians); + v_proj = p_proj - v - origin; + radians = atan2(Geom::dot(Geom::rot90(v_orig), v_proj), Geom::dot(v_orig, v_proj)); + _addSnappedPoint(sc, p_proj - v, Geom::L2(p.getPoint() - (p_proj - v)), p.getSourceType(), p.getSourceNum(), true, radians); + } + } + } 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(line1, line2); + inters = Geom::intersection(constraint_line, gridguide_line); } catch (Geom::InfiniteSolutions e) { @@ -97,23 +123,14 @@ void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, } if (inters) { - Geom::Point t = line1.pointAt((*inters).ta); + 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(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 - } + _addSnappedPoint(sc, t, dist, p.getSourceType(), p.getSourceNum(), true, 1); } } } diff --git a/src/line-snapper.h b/src/line-snapper.h index 1aa3526cc..403c8cbba 100644 --- a/src/line-snapper.h +++ b/src/line-snapper.h @@ -34,7 +34,7 @@ public: void constrainedSnap(SnappedConstraints &sc, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, - ConstraintLine const &c, + SnapConstraint const &c, std::vector const *it) const; protected: @@ -54,7 +54,7 @@ private: // Will only be implemented for guide lines, because grid lines don't have an origin virtual void _addSnappedLinesOrigin(SnappedConstraints &sc, Geom::Point const origin, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const; - virtual void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const = 0; + virtual void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap, Geom::Coord angle) const = 0; }; } diff --git a/src/live_effects/lpe-circle_3pts.cpp b/src/live_effects/lpe-circle_3pts.cpp index d600ef046..951a3b7c8 100644 --- a/src/live_effects/lpe-circle_3pts.cpp +++ b/src/live_effects/lpe-circle_3pts.cpp @@ -17,6 +17,7 @@ // You might need to include other 2geom files. You can add them here: #include <2geom/path.h> +#include <2geom/circle.h> namespace Inkscape { namespace LivePathEffect { @@ -30,24 +31,6 @@ LPECircle3Pts::~LPECircle3Pts() { } -static void _circle(Geom::Point center, double radius, std::vector &path_out) { - using namespace Geom; - - Geom::Path pb; - - D2 B; - Linear bo = Linear(0, 2 * M_PI); - - B[0] = cos(bo,4); - B[1] = sin(bo,4); - - B = B * radius + center; - - pb.append(SBasisCurve(B)); - - path_out.push_back(pb); -} - static void _circle3(Geom::Point const &A, Geom::Point const &B, Geom::Point const &C, std::vector &path_out) { using namespace Geom; @@ -64,7 +47,8 @@ static void _circle3(Geom::Point const &A, Geom::Point const &B, Geom::Point con Point M = D + v * lambda; double radius = L2(M - A); - _circle(M, radius, path_out); + Geom::Circle c(M, radius); + c.getPath(path_out); } std::vector diff --git a/src/live_effects/lpe-circle_with_radius.cpp b/src/live_effects/lpe-circle_with_radius.cpp index 574a9c004..71611e18b 100644 --- a/src/live_effects/lpe-circle_with_radius.cpp +++ b/src/live_effects/lpe-circle_with_radius.cpp @@ -18,6 +18,7 @@ #include <2geom/sbasis.h> #include <2geom/bezier-to-sbasis.h> #include <2geom/d2.h> +#include <2geom/circle.h> using namespace Geom; @@ -38,22 +39,6 @@ LPECircleWithRadius::~LPECircleWithRadius() } -void _circle(Geom::Point center, double radius, std::vector &path_out) { - Geom::Path pb; - - D2 B; - Linear bo = Linear(0, 2 * M_PI); - - B[0] = cos(bo,4); - B[1] = sin(bo,4); - - B = B * radius + center; - - pb.append(SBasisCurve(B)); - - path_out.push_back(pb); -} - std::vector LPECircleWithRadius::doEffect_path (std::vector const & path_in) { @@ -64,7 +49,8 @@ LPECircleWithRadius::doEffect_path (std::vector const & path_in) double radius = Geom::L2(pt - center); - _circle(center, radius, path_out); + Geom::Circle c(center, radius); + c.getPath(path_out); return path_out; } diff --git a/src/object-edit.cpp b/src/object-edit.cpp index 1d81aa7f5..83b01013c 100644 --- a/src/object-edit.cpp +++ b/src/object-edit.cpp @@ -141,7 +141,7 @@ RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*orig //In general we cannot just snap this radius to an arbitrary point, as we have only a single //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap //the radius then we should have a constrained snap. snap_knot_position() is unconstrained - Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0))); + Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0))); if (state & GDK_CONTROL_MASK) { gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0; @@ -191,7 +191,7 @@ RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*orig //In general we cannot just snap this radius to an arbitrary point, as we have only a single //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap //the radius then we should have a constrained snap. snap_knot_position() is unconstrained - Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1))); + Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1))); if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry, // resulting in a perfect circle (and not an ellipse) @@ -280,13 +280,13 @@ RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &or // snap to horizontal or diagonal if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) { // closer to the diagonal and in same-sign quarters, change both using ratio - s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1))); + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1))); minx = s[Geom::X] - origin[Geom::X]; miny = s[Geom::Y] - origin[Geom::Y]; rect->height.computed = MAX(h_orig + minx / ratio, 0); } else { // closer to the horizontal, change only width, height is h_orig - s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0))); + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0))); minx = s[Geom::X] - origin[Geom::X]; miny = s[Geom::Y] - origin[Geom::Y]; rect->height.computed = MAX(h_orig, 0); @@ -297,13 +297,13 @@ RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &or // snap to vertical or diagonal if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) { // closer to the diagonal and in same-sign quarters, change both using ratio - s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1))); + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1))); minx = s[Geom::X] - origin[Geom::X]; miny = s[Geom::Y] - origin[Geom::Y]; rect->width.computed = MAX(w_orig + miny * ratio, 0); } else { // closer to the vertical, change only height, width is w_orig - s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1))); + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1))); minx = s[Geom::X] - origin[Geom::X]; miny = s[Geom::Y] - origin[Geom::Y]; rect->width.computed = MAX(w_orig, 0); @@ -370,14 +370,14 @@ RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin // snap to horizontal or diagonal if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) { // closer to the diagonal and in same-sign quarters, change both using ratio - s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1))); + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1))); minx = s[Geom::X] - origin[Geom::X]; miny = s[Geom::Y] - origin[Geom::Y]; rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y); rect->height.computed = MAX(h_orig - minx / ratio, 0); } else { // closer to the horizontal, change only width, height is h_orig - s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0))); + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0))); minx = s[Geom::X] - origin[Geom::X]; miny = s[Geom::Y] - origin[Geom::Y]; rect->y.computed = MIN(origin[Geom::Y], opposite_y); @@ -389,14 +389,14 @@ RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin // snap to vertical or diagonal if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) { // closer to the diagonal and in same-sign quarters, change both using ratio - s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1))); + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1))); minx = s[Geom::X] - origin[Geom::X]; miny = s[Geom::Y] - origin[Geom::Y]; rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x); rect->width.computed = MAX(w_orig - miny * ratio, 0); } else { // closer to the vertical, change only height, width is w_orig - s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1))); + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1))); minx = s[Geom::X] - origin[Geom::X]; miny = s[Geom::Y] - origin[Geom::Y]; rect->x.computed = MIN(origin[Geom::X], opposite_x); diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index 983a6fede..f96335c4a 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -16,6 +16,7 @@ #include <2geom/point.h> #include <2geom/rect.h> #include <2geom/line.h> +#include <2geom/circle.h> #include "document.h" #include "sp-namedview.h" #include "sp-image.h" @@ -522,7 +523,7 @@ bool Inkscape::ObjectSnapper::isUnselectedNode(Geom::Point const &point, std::ve void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, Inkscape::SnapCandidatePoint const &p, - ConstraintLine const &c) const + SnapConstraint const &c) const { _collectPaths(p, p.getSourceNum() == 0); @@ -537,36 +538,54 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, direction_vector = Geom::unit_vector(direction_vector); } - // 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 snapping tolerance + // 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_proj_on_cl = p.getPoint(); // projection has already been taken care of in constrainedSnap in the snapmanager; Geom::Point const p_min_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_cl - getSnapperTolerance() * direction_vector); Geom::Point const p_max_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_cl + getSnapperTolerance() * direction_vector); + Geom::Coord tolerance = getSnapperTolerance(); - Geom::Path cl; - std::vector clv; - cl.start(p_min_on_cl); - cl.appendNew(p_max_on_cl); - clv.push_back(cl); + // 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 constraint_path; + if (c.isCircular()) { + Geom::Circle constraint_circle(_snapmanager->getDesktop()->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(p_max_on_cl); + constraint_path.push_back(constraint_line); + } for (std::vector::const_iterator k = _paths_to_snap_to->begin(); k != _paths_to_snap_to->end(); k++) { if (k->path_vector) { - Geom::CrossingSet cs = Geom::crossings(clv, *(k->path_vector)); - if (cs.size() > 0) { - // We need only the first element of cs, because cl is only a single straight linesegment - // This first element contains a vector filled with crossings of cl with k->first - for (std::vector::const_iterator m = cs[0].begin(); m != cs[0].end(); m++) { - if ((*m).ta >= 0 && (*m).ta <= 1 ) { - // Reconstruct the point of intersection - Geom::Point p_inters = p_min_on_cl + ((*m).ta) * (p_max_on_cl - p_min_on_cl); - // 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), p.getSourceType(), p.getSourceNum(), k->target_type, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true, k->target_bbox); + Geom::CrossingSet cs = Geom::crossings(constraint_path, *(k->path_vector)); + unsigned int index = 0; + for (Geom::CrossingSet::const_iterator i = cs.begin(); i != cs.end(); i++) { + if (index >= constraint_path.size()) { + break; + } + for (Geom::Crossings::const_iterator m = (*i).begin(); m != (*i).end(); m++) { + //std::cout << "ta = " << (*m).ta << " | tb = " << (*m).tb << std::endl; + // Reconstruct the point of intersection + Geom::Point p_inters = constraint_path[index].pointAt((*m).ta); + // .. and convert it to desktop coordinates + p_inters = _snapmanager->getDesktop()->doc2dt(p_inters); + Geom::Coord dist = Geom::L2(p_proj_on_cl - p_inters); + SnappedPoint s = SnappedPoint(p_inters, p.getSourceType(), p.getSourceNum(), k->target_type, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true, k->target_bbox);; + if (dist <= tolerance) { // If the intersection is within snapping range, then we might snap to it + if (c.isCircular()) { + Geom::Point v_orig = c.getDirection(); // vector from the origin to the original (untransformed) point + Geom::Point v_inters = p_inters - c.getPoint(); + Geom::Coord radians = atan2(Geom::dot(Geom::rot90(v_orig), v_inters), Geom::dot(v_orig, v_inters)); + s.setTransformation(Geom::Point(radians, radians)); + } sc.points.push_back(s); } } + index++; } } } @@ -622,7 +641,7 @@ void Inkscape::ObjectSnapper::freeSnap(SnappedConstraints &sc, void Inkscape::ObjectSnapper::constrainedSnap( SnappedConstraints &sc, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, - ConstraintLine const &c, + SnapConstraint const &c, std::vector const *it) const { if (_snap_enabled == false || _snapmanager->snapprefs.getSnapFrom(p.getSourceType()) == false) { @@ -677,7 +696,7 @@ void Inkscape::ObjectSnapper::guideFreeSnap(SnappedConstraints &sc, void Inkscape::ObjectSnapper::guideConstrainedSnap(SnappedConstraints &sc, Geom::Point const &p, Geom::Point const &guide_normal, - ConstraintLine const &/*c*/) const + SnapConstraint const &/*c*/) const { /* Get a list of all the SPItems that we will try to snap to */ std::vector cand; diff --git a/src/object-snapper.h b/src/object-snapper.h index caf643f73..454a18545 100644 --- a/src/object-snapper.h +++ b/src/object-snapper.h @@ -46,7 +46,7 @@ public: void guideConstrainedSnap(SnappedConstraints &sc, Geom::Point const &p, Geom::Point const &guide_normal, - ConstraintLine const &c) const; + SnapConstraint const &c) const; bool ThisSnapperMightSnap() const; bool GuidesMightSnap() const; @@ -63,7 +63,7 @@ public: void constrainedSnap(SnappedConstraints &sc, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, - ConstraintLine const &c, + SnapConstraint const &c, std::vector const *it) const; private: @@ -98,7 +98,7 @@ private: void _snapPathsConstrained(SnappedConstraints &sc, Inkscape::SnapCandidatePoint const &p, // in desktop coordinates - ConstraintLine const &c) const; + SnapConstraint const &c) const; bool isUnselectedNode(Geom::Point const &point, std::vector const *unselected_nodes) const; diff --git a/src/pen-context.cpp b/src/pen-context.cpp index 5b9f6808a..4a21d7bcb 100644 --- a/src/pen-context.cpp +++ b/src/pen-context.cpp @@ -1470,7 +1470,7 @@ void pen_set_to_nearest_horiz_vert(const SPPenContext *const pc, Geom::Point &pt } } else { // Create a horizontal or vertical constraint line - Inkscape::Snapper::ConstraintLine cl(origin, next_dir ? Geom::Point(0, 1) : Geom::Point(1, 0)); + Inkscape::Snapper::SnapConstraint cl(origin, next_dir ? Geom::Point(0, 1) : Geom::Point(1, 0)); // Snap along the constraint line; if we didn't snap then still the constraint will be applied SnapManager &m = pc->desktop->namedview->snap_manager; diff --git a/src/seltrans.cpp b/src/seltrans.cpp index 05f47d4ab..9c83dd63e 100644 --- a/src/seltrans.cpp +++ b/src/seltrans.cpp @@ -1209,7 +1209,7 @@ gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, Geom::P SnapManager &m = _desktop->namedview->snap_manager; m.setup(_desktop, false, _items_const); - Inkscape::Snapper::ConstraintLine const constraint(component_vectors[dim_b]); + Inkscape::Snapper::SnapConstraint const constraint(component_vectors[dim_b]); // When skewing, we cannot snap the corners of the bounding box, see the comment in "constrainedSnapSkew" for details Geom::Point const s(skew[dim_a], scale[dim_a]); Inkscape::SnappedPoint sn = m.constrainedSnapSkew(_snap_points, _point, constraint, s, _origin, Geom::Dim2(dim_b)); @@ -1276,7 +1276,10 @@ gboolean Inkscape::SelTrans::rotateRequest(Geom::Point &pt, guint state) if (fabs(h2) < 1e-15) return FALSE; Geom::Point q2 = d2 / h2; // normalized new vector to handle - double radians; + Geom::Rotate r1(q1); + Geom::Rotate r2(q2); + + double radians = atan2(Geom::dot(Geom::rot90(d1), d2), Geom::dot(d1, d2));; if (state & GDK_CONTROL_MASK) { // Snap to defined angle increments double cos_t = Geom::dot(q1, q2); @@ -1285,15 +1288,25 @@ gboolean Inkscape::SelTrans::rotateRequest(Geom::Point &pt, guint state) if (snaps) { radians = ( M_PI / snaps ) * floor( radians * snaps / M_PI + .5 ); } - q1 = Geom::Point(1, 0); - q2 = Geom::Point(cos(radians), sin(radians)); + r1 = Geom::Rotate(0); //q1 = Geom::Point(1, 0); + r2 = Geom::Rotate(radians); //q2 = Geom::Point(cos(radians), sin(radians)); } else { - radians = atan2(Geom::dot(Geom::rot90(d1), d2), - Geom::dot(d1, d2)); + SnapManager &m = _desktop->namedview->snap_manager; + m.setup(_desktop, false, _items_const); + // When rotating, we cannot snap the corners of the bounding box, see the comment in "constrainedSnapRotate" for details + Inkscape::SnappedPoint sn = m.constrainedSnapRotate(_snap_points, _point, radians, _origin); + + if (sn.getSnapped()) { + _desktop->snapindicator->set_new_snaptarget(sn); + // We snapped something, so change the rotation to reflect it + radians = sn.getTransformation()[0]; + r1 = Geom::Rotate(0); + r2 = Geom::Rotate(radians); + } else { + _desktop->snapindicator->remove_snaptarget(); + } } - Geom::Rotate const r1(q1); - Geom::Rotate const r2(q2); // Calculate the relative affine _relative_affine = r2 * r1.inverse(); @@ -1460,14 +1473,14 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state) // the constraint-line once. The constraint lines are parallel, but might not be colinear. // Therefore we will have to set the point through which the constraint-line runs // individually for each point to be snapped; this will be handled however by _snapTransformed() - s.push_back(m.constrainedSnapTranslation(_bbox_points_for_translating, + s.push_back(m.constrainedSnapTranslate(_bbox_points_for_translating, _point, - Inkscape::Snapper::ConstraintLine(component_vectors[dim]), + Inkscape::Snapper::SnapConstraint(component_vectors[dim]), dxy)); - s.push_back(m.constrainedSnapTranslation(_snap_points, + s.push_back(m.constrainedSnapTranslate(_snap_points, _point, - Inkscape::Snapper::ConstraintLine(component_vectors[dim]), + Inkscape::Snapper::SnapConstraint(component_vectors[dim]), dxy)); } else { // !control @@ -1477,8 +1490,8 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state) g_get_current_time(&starttime); */ /* Snap to things with no constraint */ - s.push_back(m.freeSnapTranslation(_bbox_points_for_translating, _point, dxy)); - s.push_back(m.freeSnapTranslation(_snap_points, _point, dxy)); + s.push_back(m.freeSnapTranslate(_bbox_points_for_translating, _point, dxy)); + s.push_back(m.freeSnapTranslate(_snap_points, _point, dxy)); /*g_get_current_time(&endtime); double elapsed = ((((double)endtime.tv_sec - starttime.tv_sec) * G_USEC_PER_SEC + (endtime.tv_usec - starttime.tv_usec))) / 1000.0; @@ -1504,7 +1517,7 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state) if (control) { // If we didn't snap, then we should still constrain horizontally or vertically // (When we did snap, then this constraint has already been enforced by - // calling constrainedSnapTranslation() above) + // calling constrainedSnapTranslate() above) if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) { dxy[Geom::Y] = 0; } else { diff --git a/src/snap.cpp b/src/snap.cpp index c47f93ff1..265b7c19a 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -335,7 +335,7 @@ Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t, Geom::Point c void SnapManager::constrainedSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap) const { Inkscape::SnappedPoint const s = constrainedSnap(Inkscape::SnapCandidatePoint(p, source_type, 0), constraint, bbox_to_snap); @@ -359,13 +359,19 @@ void SnapManager::constrainedSnapReturnByRef(Geom::Point &p, */ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapCandidatePoint const &p, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap) const { // First project the mouse pointer onto the constraint Geom::Point pp = constraint.projection(p.getPoint()); - Inkscape::SnappedPoint no_snap = Inkscape::SnappedPoint(pp, p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_CONSTRAINT, Geom::L2(pp - p.getPoint()), 0, false, true, false); + Inkscape::SnappedPoint no_snap = Inkscape::SnappedPoint(pp, p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_CONSTRAINT, NR_HUGE, 0, false, true, false); + if (constraint.isCircular()) { + Geom::Point v_orig = constraint.getDirection(); // vector from the origin to the original (untransformed) point + Geom::Point v_proj = pp - constraint.getPoint(); // vector from the origin to the projected point + Geom::Coord angle = atan2(Geom::dot(Geom::rot90(v_orig), v_proj), Geom::dot(v_orig, v_proj)); + no_snap.setTransformation(Geom::Point(angle, angle)); // Store the rotation (in radians), needed in case of snapping while rotating + } if (!someSnapperMightSnap()) { // Always return point on constraint @@ -464,7 +470,7 @@ void SnapManager::guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) // Snap to nodes or paths SnappedConstraints sc; - Inkscape::Snapper::ConstraintLine cl(guideline.point_on_line, Geom::rot90(guideline.normal_to_line)); + Inkscape::Snapper::SnapConstraint cl(guideline.point_on_line, Geom::rot90(guideline.normal_to_line)); if (object.ThisSnapperMightSnap()) { object.constrainedSnap(sc, candidate, Geom::OptRect(), cl, NULL); } @@ -509,7 +515,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( std::vector const &points, Geom::Point const &pointer, bool constrained, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Transformation transformation_type, Geom::Point const &transformation, Geom::Point const &origin, @@ -559,8 +565,17 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( g_assert(best_snapped_point.getAlwaysSnap() == false); // Check initialization of snapped point g_assert(best_snapped_point.getAtIntersection() == false); - std::vector::iterator j = transformed_points.begin(); + // Warnings for the devs + if (constrained && transformation_type == SCALE && !uniform) { + g_warning("Non-uniform constrained scaling is not supported!"); + } + + if (!constrained && transformation_type == ROTATE) { + // We do not yet allow for simultaneous rotation and scaling + g_warning("Unconstrained rotation is not supported!"); + } + std::vector::iterator j = transformed_points.begin(); // std::cout << std::endl; bool first_free_snap = true; @@ -568,27 +583,28 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( /* Snap it */ Inkscape::SnappedPoint snapped_point; - Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint; - Geom::Point const b = ((*i).getPoint() - origin); // vector to original point + Inkscape::Snapper::SnapConstraint dedicated_constraint = constraint; + Geom::Point const b = ((*i).getPoint() - origin); // vector to original point (not the transformed point! required for rotations!) if (constrained) { - if ((transformation_type == SCALE || transformation_type == STRETCH) && uniform) { + if (((transformation_type == SCALE || transformation_type == STRETCH) && uniform)) { // When uniformly scaling, each point will have its own unique constraint line, // running from the scaling origin to the original untransformed point. We will // calculate that line here - dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, b); + dedicated_constraint = Inkscape::Snapper::SnapConstraint(origin, b); + } else if (transformation_type == ROTATE) { + // Geom::L2(b) is the radius of the circular constraint + dedicated_constraint = Inkscape::Snapper::SnapConstraint(origin, b, Geom::L2(b)); } else if (transformation_type == STRETCH) { // when non-uniform stretching { - dedicated_constraint = Inkscape::Snapper::ConstraintLine((*i).getPoint(), component_vectors[dim]); - } else if (transformation_type == TRANSLATION) { + dedicated_constraint = Inkscape::Snapper::SnapConstraint((*i).getPoint(), component_vectors[dim]); + } else if (transformation_type == TRANSLATE) { // When doing a constrained translation, all points will move in the same direction, i.e. // either horizontally or vertically. The lines along which they move are therefore all - // parallel, but might not be colinear. Therefore we will have to set the point through - // which the constraint-line runs here, for each point individually. - dedicated_constraint.setPoint((*i).getPoint()); + // parallel, but might not be colinear. Therefore we will have to specify the point through + // which the constraint-line runs here, for each point individually. (we could also have done this + // earlier on, e.g. in seltrans.cpp but we're being lazy there and don't want to add an iteration loop) + dedicated_constraint = Inkscape::Snapper::SnapConstraint((*i).getPoint(), constraint.getDirection()); } // else: leave the original constraint, e.g. for skewing - if (transformation_type == SCALE && !uniform) { - g_warning("Non-uniform constrained scaling is not supported!"); - } snapped_point = constrainedSnap(*j, dedicated_constraint, bbox); } else { bool const c1 = fabs(b[Geom::X]) < 1e-6; @@ -597,7 +613,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( // When scaling, a point aligned either horizontally or vertically with the origin can only // move in that specific direction; therefore it should only snap in that direction, otherwise // we will get snapped points with an invalid transformation - dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, component_vectors[c1]); + dedicated_constraint = Inkscape::Snapper::SnapConstraint(origin, component_vectors[c1]); snapped_point = constrainedSnap(*j, dedicated_constraint, bbox); } else { // If we have a collection of SnapCandidatePoints, with mixed constrained snapping and free snapping @@ -627,7 +643,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( //Geom::Point const b = (*i - origin); // vector to original point switch (transformation_type) { - case TRANSLATION: + case TRANSLATE: result = snapped_point.getPoint() - (*i).getPoint(); /* Consider the case in which a box is almost aligned with a grid in both * horizontal and vertical directions. The distance to the intersection of @@ -693,6 +709,12 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( snapped_point.setSnapDistance(std::abs(result[0] - transformation[0])); snapped_point.setSecondSnapDistance(NR_HUGE); break; + case ROTATE: + result = snapped_point.getTransformation(); + // Store the metric for this transformation as a virtual distance (we're storing an angle) + snapped_point.setSnapDistance(std::abs(result[0] - transformation[0])); + snapped_point.setSecondSnapDistance(NR_HUGE); + break; default: g_assert_not_reached(); } @@ -738,22 +760,21 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( * \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics. */ -Inkscape::SnappedPoint SnapManager::freeSnapTranslation(std::vector const &p, +Inkscape::SnappedPoint SnapManager::freeSnapTranslate(std::vector const &p, Geom::Point const &pointer, Geom::Point const &tr) const { if (p.size() == 1) { - Geom::Point pt = _transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + Geom::Point pt = _transformPoint(p.at(0), TRANSLATE, tr, Geom::Point(0,0), Geom::X, false); _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } - return _snapTransformed(p, pointer, false, Geom::Point(0,0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + return _snapTransformed(p, pointer, false, Geom::Point(0,0), TRANSLATE, tr, Geom::Point(0,0), Geom::X, false); } /** * \brief Apply a translation to a set of points and try to snap along a constraint * - * \param point_type Category of points to which the source point belongs: node or bounding box. * \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source. * \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed). * \param constraint The direction or line along which snapping must occur. @@ -761,24 +782,23 @@ Inkscape::SnappedPoint SnapManager::freeSnapTranslation(std::vector const &p, +Inkscape::SnappedPoint SnapManager::constrainedSnapTranslate(std::vector const &p, Geom::Point const &pointer, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::Point const &tr) const { if (p.size() == 1) { - Geom::Point pt = _transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + Geom::Point pt = _transformPoint(p.at(0), TRANSLATE, tr, Geom::Point(0,0), Geom::X, false); _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } - return _snapTransformed(p, pointer, true, constraint, TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + return _snapTransformed(p, pointer, true, constraint, TRANSLATE, tr, Geom::Point(0,0), Geom::X, false); } /** * \brief Apply a scaling to a set of points and try to snap freely in 2 degrees-of-freedom * - * \param point_type Category of points to which the source point belongs: node or bounding box. * \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source. * \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed). * \param s Proposed scaling; the final scaling can only be calculated after snapping has occurred @@ -803,7 +823,6 @@ Inkscape::SnappedPoint SnapManager::freeSnapScale(std::vector const &p, Geom::Point const &pointer, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::Point const &s, Geom::Point const &o, Geom::Dim2 d) const @@ -892,6 +909,36 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(std::vector const &p, + Geom::Point const &pointer, + Geom::Coord const &angle, + Geom::Point const &o) const +{ + // Snapping the nodes of the bounding box of a selection that is being transformed, will only work if + // the transformation of the bounding box is equal to the transformation of the individual nodes. This is + // NOT the case for example when rotating or skewing. The bounding box itself cannot possibly rotate or skew, + // so it's corners have a different transformation. The snappers cannot handle this, therefore snapping + // of bounding boxes is not allowed here. + + if (p.size() == 1) { + Geom::Point pt = _transformPoint(p.at(0), ROTATE, Geom::Point(angle, angle), o, Geom::X, false); + _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); + } + + return _snapTransformed(p, pointer, true, Geom::Point(0,0), ROTATE, Geom::Point(angle, angle), o, Geom::X, false); + +} + /** * \brief Given a set of possible snap targets, find the best target (which is not necessarily * also the nearest target), and show the snap indicator if requested @@ -1116,7 +1163,7 @@ Geom::Point SnapManager::_transformPoint(Inkscape::SnapCandidatePoint const &p, /* Work out the transformed version of this point */ Geom::Point transformed; switch (transformation_type) { - case TRANSLATION: + case TRANSLATE: transformed = p.getPoint() + transformation; break; case SCALE: @@ -1141,6 +1188,10 @@ Geom::Point SnapManager::_transformPoint(Inkscape::SnapCandidatePoint const &p, // Apply that scale factor here transformed[1-dim] = (p.getPoint() - origin)[1 - dim] * transformation[1] + origin[1 - dim]; break; + case ROTATE: + // for rotations: transformation[0] stores the angle in radians + transformed = (p.getPoint() - origin) * Geom::Rotate(transformation[0]) + origin; + break; default: g_assert_not_reached(); } diff --git a/src/snap.h b/src/snap.h index 8a5688bea..26e599cc6 100644 --- a/src/snap.h +++ b/src/snap.h @@ -67,10 +67,11 @@ class SnapManager { public: enum Transformation { - TRANSLATION, + TRANSLATE, SCALE, STRETCH, - SKEW + SKEW, + ROTATE }; SnapManager(SPNamedView const *v); @@ -113,23 +114,23 @@ public: // point, by overwriting p, if snapping has occurred; otherwise p is untouched void constrainedSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; Inkscape::SnappedPoint constrainedSnap(Inkscape::SnapCandidatePoint const &p, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; void guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, SPGuideDragType drag_type) const; void guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) const; - Inkscape::SnappedPoint freeSnapTranslation(std::vector const &p, + Inkscape::SnappedPoint freeSnapTranslate(std::vector const &p, Geom::Point const &pointer, Geom::Point const &tr) const; - Inkscape::SnappedPoint constrainedSnapTranslation(std::vector const &p, + Inkscape::SnappedPoint constrainedSnapTranslate(std::vector const &p, Geom::Point const &pointer, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::Point const &tr) const; Inkscape::SnappedPoint freeSnapScale(std::vector const &p, @@ -151,11 +152,16 @@ public: Inkscape::SnappedPoint constrainedSnapSkew(std::vector const &p, Geom::Point const &pointer, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::Point const &s, // s[0] = skew factor, s[1] = scale factor Geom::Point const &o, Geom::Dim2 d) const; + Inkscape::SnappedPoint constrainedSnapRotate(std::vector const &p, + Geom::Point const &pointer, + Geom::Coord const &angle, + Geom::Point const &o) const; + Inkscape::GuideSnapper guide; ///< guide snapper Inkscape::ObjectSnapper object; ///< snapper to other objects Inkscape::SnapPreferences snapprefs; @@ -181,14 +187,11 @@ private: SPDesktop const *_desktop; bool _snapindicator; ///< When true, an indicator will be drawn at the position that was being snapped to std::vector *_unselected_nodes; ///< Nodes of the path that is currently being edited and which have not been selected and which will therefore be stationary. Only these nodes will be considered for snapping to. Of each unselected node both the position (Geom::Point) and the type (Inkscape::SnapTargetType) will be stored - //TODO: Make _unselected_nodes type safe; in the line above int is used for Inkscape::SnapTargetType, but if I remember - //correctly then in other cases the int is being used for Inkscape::SnapSourceType, or for both. How to make - //this type safe? Inkscape::SnappedPoint _snapTransformed(std::vector const &points, Geom::Point const &pointer, bool constrained, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Transformation transformation_type, Geom::Point const &transformation, Geom::Point const &origin, diff --git a/src/snapper.h b/src/snapper.h index b5bb17de9..d8214db80 100644 --- a/src/snapper.h +++ b/src/snapper.h @@ -15,6 +15,7 @@ #include #include #include +#include // for g_assert #include "snapped-point.h" #include "snapped-line.h" @@ -63,18 +64,29 @@ public: std::vector const */*it*/, std::vector */*unselected_nodes*/) const {}; - class ConstraintLine + // Class for storing the constraint for constrained snapping; can be + // - a line (infinite line with origin, running through _point pointing in _direction) + // - a direction (infinite line without origin, i.e. only a direction vector, stored in _direction) + // - a circle (_point denotes the center, _radius doesn't need an explanation, _direction contains + // the vector from the origin to the original untransformed point); + class SnapConstraint { + private: + enum SnapConstraintType {LINE, DIRECTION, CIRCLE}; + public: - ConstraintLine(Geom::Point const &d) : _has_point(false), _direction(d) {} - ConstraintLine(Geom::Point const &p, Geom::Point const &d) : _has_point(true), _point(p), _direction(d) {} - ConstraintLine(Geom::Line const &l) : _has_point(true), _point(l.origin()), _direction(l.versor()) {} + // Constructs a direction constraint, e.g. horizontal or vertical but without a specified point + SnapConstraint(Geom::Point const &d) : _direction(d), _type(DIRECTION) {} + // Constructs a linear constraint + SnapConstraint(Geom::Point const &p, Geom::Point const &d) : _point(p), _direction(d), _type(LINE) {} + SnapConstraint(Geom::Line const &l) : _point(l.origin()), _direction(l.versor()), _type(LINE) {} + // Constructs a circular constraint + SnapConstraint(Geom::Point const &p, Geom::Point const &d, Geom::Coord const &r) : _point(p), _direction(d), _radius(r), _type(CIRCLE) {} - bool hasPoint() const { - return _has_point; - } + bool hasPoint() const {return _type != DIRECTION;} Geom::Point getPoint() const { + g_assert(_type != DIRECTION); return _point; } @@ -82,28 +94,45 @@ public: return _direction; } - void setPoint(Geom::Point const &p) { - _point = p; - _has_point = true; + Geom::Coord getRadius() const { + g_assert(_type == CIRCLE); + return _radius; } - Geom::Point projection(Geom::Point const &p) const { // returns the projection of p on this constraintline - Geom::Point const p1_on_cl = _has_point ? _point : p; - Geom::Point const p2_on_cl = p1_on_cl + _direction; - return Geom::projection(p, Geom::Line(p1_on_cl, p2_on_cl)); + bool isCircular() const { return _type == CIRCLE; } + bool isLinear() const { return _type == LINE; } + bool isDirection() const { return _type == DIRECTION; } + + Geom::Point projection(Geom::Point const &p) const { // returns the projection of p on this constraint + if (_type == CIRCLE) { + // project on to a circular constraint + Geom::Point v_orig = p - _point; + Geom::Coord l = Geom::L2(v_orig); + if (l > 0) { + return _point + _radius * v_orig/l; // Length of _direction is equal to the radius + } else { + // point to be projected is exactly at the center of the circle, so any point on the circle is a projection + return _point + Geom::Point(_radius, 0); + } + } else { + // project on to a linear constraint + Geom::Point const p1_on_cl = (_type == LINE) ? _point : p; + Geom::Point const p2_on_cl = p1_on_cl + _direction; + return Geom::projection(p, Geom::Line(p1_on_cl, p2_on_cl)); + } } private: - - bool _has_point; Geom::Point _point; Geom::Point _direction; + Geom::Coord _radius; + SnapConstraintType _type; }; virtual void constrainedSnap(SnappedConstraints &/*sc*/, Inkscape::SnapCandidatePoint const &/*p*/, Geom::OptRect const &/*bbox_to_snap*/, - ConstraintLine const &/*c*/, + SnapConstraint const &/*c*/, std::vector const */*it*/) const {}; protected: diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp index 9c9c58ff1..886ddd1be 100644 --- a/src/ui/tool/node.cpp +++ b/src/ui/tool/node.cpp @@ -266,7 +266,7 @@ void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event) Node *node_away = (this == &_parent->_front ? _parent->_prev() : _parent->_next()); if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) { - Inkscape::Snapper::ConstraintLine cl(_parent->position(), + Inkscape::Snapper::SnapConstraint cl(_parent->position(), _parent->position() - node_away->position()); Inkscape::SnappedPoint p; p = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, SNAPSOURCE_NODE_HANDLE), cl); @@ -974,7 +974,7 @@ void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event) // with Ctrl+Alt, constrain to handle lines // project the new position onto a handle line that is closer boost::optional front_point, back_point; - boost::optional line_front, line_back; + boost::optional line_front, line_back; if (_front.isDegenerate()) { if (_is_line_segment(this, _next())) front_point = _next()->position() - origin; @@ -988,9 +988,9 @@ void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event) back_point = _back.relativePos(); } if (front_point) - line_front = Inkscape::Snapper::ConstraintLine(origin, *front_point); + line_front = Inkscape::Snapper::SnapConstraint(origin, *front_point); if (back_point) - line_back = Inkscape::Snapper::ConstraintLine(origin, *back_point); + line_back = Inkscape::Snapper::SnapConstraint(origin, *back_point); // TODO: combine the snap and non-snap branches by modifying snap.h / snap.cpp if (snap) { @@ -1029,8 +1029,8 @@ void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event) // with Ctrl, constrain to axes // TODO combine the two branches if (snap) { - Inkscape::Snapper::ConstraintLine line_x(origin, Geom::Point(1, 0)); - Inkscape::Snapper::ConstraintLine line_y(origin, Geom::Point(0, 1)); + Inkscape::Snapper::SnapConstraint line_x(origin, Geom::Point(1, 0)); + Inkscape::Snapper::SnapConstraint line_y(origin, Geom::Point(0, 1)); fp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()), line_x); bp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()), line_y); }