Code

Implement constrained snapping when dragging the position and size handles of a recta...
authordvlierop2 <dvlierop2@users.sourceforge.net>
Sun, 15 Mar 2009 21:08:02 +0000 (21:08 +0000)
committerdvlierop2 <dvlierop2@users.sourceforge.net>
Sun, 15 Mar 2009 21:08:02 +0000 (21:08 +0000)
src/context-fns.cpp
src/draw-context.cpp
src/knot-holder-entity.cpp
src/knot-holder-entity.h
src/nodepath.cpp
src/object-edit.cpp
src/object-snapper.cpp
src/snap.cpp
src/snap.h
src/snapper.h

index 50942b80a77b1ffbe58329ef3fb7e25a866328c5..8e4b6384cfdcfca51e35df41b8c43b8df4b58a8d 100644 (file)
@@ -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::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[0]), Inkscape::SNAPSOURCE_HANDLE,
-                                     Inkscape::Snapper::ConstraintLine(p[0] - p[1]));
+                                     Inkscape::Snapper::ConstraintLine(p[0] - p[1]), false);
 
             /* Try to snap p[1] (the dragged corner) along the constraint vector */
             s[1] = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]), Inkscape::SNAPSOURCE_HANDLE,
-                                     Inkscape::Snapper::ConstraintLine(p[1] - p[0]));
+                                     Inkscape::Snapper::ConstraintLine(p[1] - p[0]), false);
 
             /* 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::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]), Inkscape::SNAPSOURCE_HANDLE,
-                                          Inkscape::Snapper::ConstraintLine(p[1] - p[0]));
+                                          Inkscape::Snapper::ConstraintLine(p[1] - p[0]), false);
             if (snappoint.getSnapped()) {
                 p[1] = snappoint.getPoint();
             }
index 3891ce331818e1442fec6d396b6487ae4ec38ca0..955aeeae299f62a4ee1c0a2f5c5caa890aacaccc 100644 (file)
@@ -512,7 +512,7 @@ void spdc_endpoint_snap_rotation(SPEventContext const *const ec, Geom::Point &p,
             SnapManager &m = SP_EVENT_CONTEXT_DESKTOP(ec)->namedview->snap_manager;
             m.setup(SP_EVENT_CONTEXT_DESKTOP(ec));
             Geom::Point pt2g = to_2geom(p);
-            m.constrainedSnapReturnByRef( Inkscape::SnapPreferences::SNAPPOINT_NODE, pt2g, Inkscape::SNAPSOURCE_HANDLE, Inkscape::Snapper::ConstraintLine(best));
+            m.constrainedSnapReturnByRef( Inkscape::SnapPreferences::SNAPPOINT_NODE, pt2g, Inkscape::SNAPSOURCE_HANDLE, Inkscape::Snapper::ConstraintLine(best), false);
             p = from_2geom(pt2g);
         }
     }
index 4225dd9e3b19eb15292f8865bc967e527ce6db3a..6e4ecae83b5178aa7ed5929acb931c22ffbe7fd6 100644 (file)
@@ -98,14 +98,14 @@ 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::ConstraintLine const &constraint, bool const snap_projection)
 {
     Geom::Matrix const i2d (sp_item_i2d_affine(item));
     Geom::Point s = p * i2d;
     Inkscape::Snapper::ConstraintLine transformed_constraint = Inkscape::Snapper::ConstraintLine(constraint.getPoint() * i2d, (constraint.getPoint() + constraint.getDirection()) * i2d - constraint.getPoint() * i2d);
     SnapManager &m = desktop->namedview->snap_manager;
     m.setup(desktop, true, item);
-    m.constrainedSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, s, Inkscape::SNAPSOURCE_HANDLE, transformed_constraint);
+    m.constrainedSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, s, Inkscape::SNAPSOURCE_HANDLE, transformed_constraint, snap_projection);
     return s * i2d.inverse();
 }
 
index c8fd29ddf3dc15fc97e0c19b877eb5a93f30cfad..7414f3d45f4973483a0ff017e22a8d1c40798c9f 100644 (file)
@@ -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::ConstraintLine const &constraint, bool const snap_projection);
 
     SPKnot *knot;
     SPItem *item;
index 51d4b9bef8395d07f904881e1644e3a589934948..1899f7978646954de604f1ee60d0e518fa312c48 100644 (file)
@@ -1394,7 +1394,7 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath,
                    if (constrained) {
                        Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
                        dedicated_constraint.setPoint(n->pos);
-                       s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type, dedicated_constraint);
+                       s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type, dedicated_constraint, false);
                    } else {
                        s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type);
                    }
@@ -3962,7 +3962,7 @@ static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, g
                 p = n->pos + (scal / linelen) * ndelta;
             }
             if ((state & GDK_SHIFT_MASK) == 0) {
-                s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type, Inkscape::Snapper::ConstraintLine(p, ndelta));
+                s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type, Inkscape::Snapper::ConstraintLine(p, ndelta), false);
             }
         } else {
             if ((state & GDK_SHIFT_MASK) == 0) {
index 0719a59d319e2f2fc30cb7bd877356f5d66a19e5..e88550a0a4debfb2d24a32789a1706176e60e466 100644 (file)
@@ -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::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)), false);
 
     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::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)), false);
 
     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)
@@ -259,7 +259,8 @@ void
 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
 {
     SPRect *rect = SP_RECT(item);
-    Geom::Point const s = snap_knot_position(p);
+
+    Geom::Point s = p;
 
     if (state & GDK_CONTROL_MASK) {
         // original width/height when drag started
@@ -267,41 +268,56 @@ RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &or
         gdouble const h_orig = (origin[Geom::Y] - rect->y.computed);
 
         //original ratio
-        gdouble const ratio = (w_orig / h_orig);
+        gdouble ratio = (w_orig / h_orig);
 
         // mouse displacement since drag started
-        gdouble const minx = s[Geom::X] - origin[Geom::X];
-        gdouble const miny = s[Geom::Y] - origin[Geom::Y];
+        gdouble minx = p[Geom::X] - origin[Geom::X];
+        gdouble miny = p[Geom::Y] - origin[Geom::Y];
 
-        if (fabs(minx) > fabs(miny)) {
+        Geom::Point p_handle(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
 
-            // snap to horizontal or diagonal
-            rect->width.computed = MAX(w_orig + minx, 0);
+        if (fabs(minx) > fabs(miny)) {
+               // 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
-                rect->height.computed = MAX(h_orig + minx / ratio, 0);
+               s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)), true);
+                               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
-                rect->height.computed = MAX(h_orig, 0);
+               s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0)), true);
+                               minx = s[Geom::X] - origin[Geom::X];
+                               miny = s[Geom::Y] - origin[Geom::Y];
+                               rect->height.computed = MAX(h_orig, 0);
             }
+            rect->width.computed = MAX(w_orig + minx, 0);
 
         } else {
             // snap to vertical or diagonal
-            rect->height.computed = MAX(h_orig + miny, 0);
             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
-                rect->width.computed = MAX(w_orig + miny * ratio, 0);
+               s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)), true);
+                               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
-                rect->width.computed = MAX(w_orig, 0);
+               s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1)), true);
+                               minx = s[Geom::X] - origin[Geom::X];
+                               miny = s[Geom::Y] - origin[Geom::Y];
+                               rect->width.computed = MAX(w_orig, 0);
             }
+            rect->height.computed = MAX(h_orig + miny, 0);
+
         }
 
         rect->width._set = rect->height._set = true;
 
     } else {
         // move freely
-        rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
+       s = snap_knot_position(p);
+       rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
         rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0);
         rect->width._set = rect->height._set = true;
     }
@@ -339,53 +355,66 @@ RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin
     gdouble w_orig = opposite_x - origin[Geom::X];
     gdouble h_orig = opposite_y - origin[Geom::Y];
 
-    Geom::Point const s = snap_knot_position(p);
+    Geom::Point s = p;
+    Geom::Point p_handle(rect->x.computed, rect->y.computed);
 
     // mouse displacement since drag started
-    gdouble minx = s[Geom::X] - origin[Geom::X];
-    gdouble miny = s[Geom::Y] - origin[Geom::Y];
+    gdouble minx = p[Geom::X] - origin[Geom::X];
+    gdouble miny = p[Geom::Y] - origin[Geom::Y];
 
     if (state & GDK_CONTROL_MASK) {
         //original ratio
         gdouble ratio = (w_orig / h_orig);
 
         if (fabs(minx) > fabs(miny)) {
-
-            // snap to horizontal or diagonal
-            rect->x.computed = MIN(s[Geom::X], opposite_x);
-            rect->width.computed = MAX(w_orig - minx, 0);
+               // 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
-                rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
+               s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)), true);
+                               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
-                rect->y.computed = MIN(origin[Geom::Y], opposite_y);
+               s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0)), true);
+                               minx = s[Geom::X] - origin[Geom::X];
+                               miny = s[Geom::Y] - origin[Geom::Y];
+               rect->y.computed = MIN(origin[Geom::Y], opposite_y);
                 rect->height.computed = MAX(h_orig, 0);
             }
-
+            rect->x.computed = MIN(s[Geom::X], opposite_x);
+            rect->width.computed = MAX(w_orig - minx, 0);
         } else {
-
             // snap to vertical or diagonal
-            rect->y.computed = MIN(s[Geom::Y], opposite_y);
-            rect->height.computed = MAX(h_orig - miny, 0);
-            if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
+               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
-                rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x);
+               s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)), true);
+                               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
-                rect->x.computed = MIN(origin[Geom::X], opposite_x);
+               s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1)), true);
+                               minx = s[Geom::X] - origin[Geom::X];
+                               miny = s[Geom::Y] - origin[Geom::Y];
+                               rect->x.computed = MIN(origin[Geom::X], opposite_x);
                 rect->width.computed = MAX(w_orig, 0);
             }
-
+            rect->y.computed = MIN(s[Geom::Y], opposite_y);
+                       rect->height.computed = MAX(h_orig - miny, 0);
         }
 
         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
 
     } else {
         // move freely
-        rect->x.computed = MIN(s[Geom::X], opposite_x);
+       s = snap_knot_position(p);
+       minx = s[Geom::X] - origin[Geom::X];
+               miny = s[Geom::Y] - origin[Geom::Y];
+
+       rect->x.computed = MIN(s[Geom::X], opposite_x);
         rect->width.computed = MAX(w_orig - minx, 0);
         rect->y.computed = MIN(s[Geom::Y], opposite_y);
         rect->height.computed = MAX(h_orig - miny, 0);
index b02b9786af7d12e00c4bc060febbdc55b7527daa..e0506c8e2a63509d136da6500694b599e34cb5f5 100644 (file)
@@ -548,13 +548,10 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc,
         direction_vector = Geom::unit_vector(direction_vector);
     }
 
-    Geom::Point const p1_on_cl = c.hasPoint() ? c.getPoint() : p;
-    Geom::Point const p2_on_cl = p1_on_cl + 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
-    Geom::Point const p_proj_on_cl = Geom::projection(p, Geom::Line(p1_on_cl, p2_on_cl));
+    Geom::Point const p_proj_on_cl = c.projection(p);
     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);
 
index a22f25ab0afe01bfd85c2961dae0d5a39f77fff0..6cd89cb0bc69759523c286559498a2687aaea5bb 100644 (file)
@@ -264,10 +264,11 @@ void SnapManager::constrainedSnapReturnByRef(Inkscape::SnapPreferences::PointTyp
                                                                                                        Geom::Point &p,
                                                                                                        Inkscape::SnapSourceType const source_type,
                                                     Inkscape::Snapper::ConstraintLine const &constraint,
+                                                    bool const snap_projection,
                                                     bool first_point,
                                                     Geom::OptRect const &bbox_to_snap) const
 {
-    Inkscape::SnappedPoint const s = constrainedSnap(point_type, p, source_type, constraint, first_point, bbox_to_snap);
+    Inkscape::SnappedPoint const s = constrainedSnap(point_type, p, source_type, constraint, snap_projection, first_point, bbox_to_snap);
     s.getPoint(p);
 }
 
@@ -288,6 +289,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P
                                                                                                        Geom::Point const &p,
                                                                                                        Inkscape::SnapSourceType const &source_type,
                                                     Inkscape::Snapper::ConstraintLine const &constraint,
+                                                    bool snap_projection,
                                                     bool first_point,
                                                     Geom::OptRect const &bbox_to_snap) const
 {
@@ -310,10 +312,12 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P
         items_to_ignore = _items_to_ignore;
     }
 
+    Geom::Point pp = constraint.projection(p);
+
     SnappedConstraints sc;
     SnapperList const snappers = getSnappers();
     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
-        (*i)->constrainedSnap(sc, point_type, p, source_type, first_point, bbox_to_snap, constraint, items_to_ignore);
+        (*i)->constrainedSnap(sc, point_type, pp, source_type, first_point, bbox_to_snap, constraint, items_to_ignore);
     }
 
     if (_item_to_ignore) {
@@ -456,7 +460,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed(
             if (transformation_type == SCALE && !uniform) {
                 g_warning("Non-uniform constrained scaling is not supported!");
             }
-            snapped_point = constrainedSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), dedicated_constraint, i == points.begin(), bbox);
+            snapped_point = constrainedSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), dedicated_constraint, false, i == points.begin(), bbox);
         } else {
             bool const c1 = fabs(b[Geom::X]) < 1e-6;
             bool const c2 = fabs(b[Geom::Y]) < 1e-6;
@@ -465,7 +469,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed(
                 // 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]);
-                snapped_point = constrainedSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), dedicated_constraint, i == points.begin(), bbox);
+                snapped_point = constrainedSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), dedicated_constraint, false, i == points.begin(), bbox);
             } else {
                 snapped_point = freeSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), i == points.begin(), bbox);
             }
index ac314c66d2d4da16483529c731ba1fa2a7e951af..5290ef081ed3f90a98012439900b2981911cf573 100644 (file)
@@ -86,6 +86,7 @@ public:
                                                                        Geom::Point &p,
                                                                        Inkscape::SnapSourceType const source_type,
                                                                        Inkscape::Snapper::ConstraintLine const &constraint,
+                                                                       bool snap_projection, //try snapping the projection of p onto the constraint line, not p itself
                                                                        bool first_point = true,
                                                                        Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const;
 
@@ -93,6 +94,7 @@ public:
                                                                                   Geom::Point const &p,
                                                                                   Inkscape::SnapSourceType const &source_type,
                                                                                   Inkscape::Snapper::ConstraintLine const &constraint,
+                                                                                  bool const snap_projection,
                                            bool first_point = true,
                                            Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const;
 
index 0110a9154a92d399d2f8c689e96c594373ed49c3..cbd52f0523d786399a3372e04a5a20d9503fadfe 100644 (file)
@@ -86,6 +86,12 @@ public:
             _has_point = true;
         }
 
+        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));
+        }
+
     private:
 
         bool _has_point;