Code

Split SPCanvasItem and SPCanvasGroup to individual .h files. Removed forward header.
[inkscape.git] / src / live_effects / lpe-knot.cpp
index 678bbf8ba070a0aaad8c2b9f69e8f36e5b507fce..fef4e1f8777573e3e58e7ceadcd65caff833d2e7 100644 (file)
-#define INKSCAPE_LPE_KNOT_CPP
-/** \file
- * LPE <knot> implementation
+/** @file
+ * @brief LPE knot effect implementation
  */
-/*
- * Authors:
- *   JF Barraud
-*
-* Copyright (C) JF Barraud 2007 <jf.barraud@gmail.com>
+/* Authors:
+ *   Jean-Francois Barraud <jf.barraud@gmail.com>
+ *   Abhishek Sharma
+ *
+ * Copyright (C) 2007 Authors
  *
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
+#include "sp-shape.h"
+#include "sp-path.h"
+#include "display/curve.h"
 #include "live_effects/lpe-knot.h"
+#include "svg/svg.h"
+#include "style.h"
+#include "knot-holder-entity.h"
 
-#include <2geom/path.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/sbasis.h>
 #include <2geom/d2.h>
-#include <2geom/crossing.h>
-#include <2geom/path-intersection.h>
+#include <2geom/d2-sbasis.h>
+#include <2geom/path.h>
+//#include <2geom/crossing.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/exception.h>
+
+// for change crossing undo
+#include "verbs.h"
+#include "document.h"
+
+#include <exception>
 
 namespace Inkscape {
 namespace LivePathEffect {
 
-LPEKnot::LPEKnot(LivePathEffectObject *lpeobject) :
-    Effect(lpeobject),
-    // initialise your parameters here:
-    interruption_width(_("Gap width"), _("The width of the gap in the path where it self-intersects"), "interruption_width", &wr, this, 10)
+class KnotHolderEntityCrossingSwitcher : public LPEKnotHolderEntity
 {
-    // register all your parameters here, so Inkscape knows which parameters this effect has:
-    registerParameter( dynamic_cast<Parameter *>(&interruption_width) );
-}
+public:
+    virtual ~KnotHolderEntityCrossingSwitcher() {}
+
+    virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
+    virtual Geom::Point knot_get();
+    virtual void knot_click(guint state);
+};
 
-LPEKnot::~LPEKnot()
-{
 
+Geom::Path::size_type size_nondegenerate(Geom::Path const &path) {
+    Geom::Path::size_type retval = path.size_open();
+
+    // if path is closed and closing segment is not degenerate
+    if (path.closed() && !path.back_closed().isDegenerate()) {
+        retval = path.size_closed();
+    }
+
+    return retval;
 }
 
+//---------------------------------------------------------------------------
+//LPEKnot specific Interval manipulation.
+//---------------------------------------------------------------------------
+
 //remove an interval from an union of intervals.
+//TODO: is it worth moving it to 2Geom?
+static
 std::vector<Geom::Interval> complementOf(Geom::Interval I, std::vector<Geom::Interval> domain){
     std::vector<Geom::Interval> ret;
-    double min = domain.front().min();
-    double max = domain.back().max();
-    Geom::Interval I1 = Geom::Interval(min,I.min());
-    Geom::Interval I2 = Geom::Interval(I.max(),max);
-
-    for (unsigned i = 0; i<domain.size(); i++){
-        boost::optional<Geom::Interval> I1i = intersect(domain.at(i),I1);
-        if (I1i) ret.push_back(I1i.get());
-        boost::optional<Geom::Interval> I2i = intersect(domain.at(i),I2);
-        if (I2i) ret.push_back(I2i.get());
+    if (!domain.empty()) {
+        double min = domain.front().min();
+        double max = domain.back().max();
+        Geom::Interval I1 = Geom::Interval(min,I.min());
+        Geom::Interval I2 = Geom::Interval(I.max(),max);
+
+        for (unsigned i = 0; i<domain.size(); i++){
+            boost::optional<Geom::Interval> I1i = intersect(domain.at(i),I1);
+            if (I1i && !I1i->isSingular()) ret.push_back(I1i.get());
+            boost::optional<Geom::Interval> I2i = intersect(domain.at(i),I2);
+            if (I2i && !I2i->isSingular()) ret.push_back(I2i.get());
+        }
     }
     return ret;
 }
 
+//find the time interval during which patha is hidden by pathb near a given crossing.
+// Warning: not accurate!
+static
 Geom::Interval
-findShadowedTime(Geom::Path &patha, 
-                 Geom::Path &pathb, 
-                 Geom::Crossing crossing, 
-                 unsigned idx, double width){
+findShadowedTime(Geom::Path const &patha, std::vector<Geom::Point> const &pt_and_dir,
+                 double const ta, double const width){
     using namespace Geom;
-    double curveidx, timeoncurve = modf(crossing.getOtherTime(idx),&curveidx);
-    if(curveidx == pathb.size() && timeoncurve == 0) { curveidx--; timeoncurve = 0.99999;}
-    assert(curveidx >= 0 && curveidx < pathb.size());
-    
-    std::vector<Point> MV = pathb[unsigned(curveidx)].pointAndDerivatives(timeoncurve,1);
-    Point T = unit_vector(MV.at(1));
+    Point T = unit_vector(pt_and_dir[1]);
     Point N = T.cw();
-    Point A = MV.at(0)-10*width*T, B = MV.at(0)+10*width*T;
-    
-    std::vector<Geom::Path> cutter;
-    Geom::Path cutterLeft  = Geom::Path();
-    Geom::Path cutterRight = Geom::Path();
-    cutterLeft.append (LineSegment (A-width*N, B-width*N));
-    cutterRight.append(LineSegment (A+width*N, B+width*N));
-    cutter.push_back(cutterLeft);
-    cutter.push_back(cutterRight);
+    Point A = pt_and_dir[0]-3*width*T, B = A+6*width*T;
+
+    Matrix mat = from_basis( T, N, pt_and_dir[0] );
+    mat = mat.inverse();
+    Path p = patha * mat;
     
-    std::vector<Geom::Path> patha_as_vect = std::vector<Geom::Path>(1,patha);
+    std::vector<double> times;
     
-    CrossingSet crossingTable = crossings (patha_as_vect, cutter);
-    double t0 = crossing.getTime(idx);
-    double tmin = 0,tmax = patha.size()-0.0001;
-    assert(crossingTable.size()>=1);
-    for (unsigned c=0; c<crossingTable.front().size(); c++){
-        double t = crossingTable.front().at(c).ta;
-        assert(crossingTable.front().at(c).a==0);
-        if (t>tmin and t<t0) tmin = t;
-        if (t<tmax and t>t0) tmax = t;
+    //TODO: explore the path fwd/backward from ta (worth?)
+    for (unsigned i = 0; i < size_nondegenerate(patha); i++){
+        D2<SBasis> f = p[i].toSBasis();
+        std::vector<double> times_i, temptimes;
+        temptimes = roots(f[Y]-width);
+        times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() ); 
+        temptimes = roots(f[Y]+width);
+        times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() ); 
+        temptimes = roots(f[X]-3*width);
+        times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() ); 
+        temptimes = roots(f[X]+3*width);
+        times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
+        for (unsigned k=0; k<times_i.size(); k++){
+            times_i[k]+=i;
+        }
+        times.insert(times.end(), times_i.begin(), times_i.end() );
+    }
+    std::sort( times.begin(),  times.end() );
+    std::vector<double>::iterator new_end = std::unique( times.begin(),  times.end() );
+    times.resize( new_end - times.begin() );
+
+    double tmin = 0, tmax = size_nondegenerate(patha);
+    double period = size_nondegenerate(patha);
+    if (times.size()>0){
+        unsigned rk = upper_bound( times.begin(),  times.end(), ta ) - times.begin();
+        if ( rk < times.size() ) 
+            tmax = times[rk];
+        else if ( patha.closed() ) 
+            tmax = times[0]+period;
+
+        if ( rk > 0 ) 
+            tmin = times[rk-1];
+        else if ( patha.closed() ) 
+            tmin = times.back()-period;
     }
-    //return Interval(t0-0.1,t0+0.1);
     return Interval(tmin,tmax);
-}                
+}
 
-//Just a try; this should be moved to 2geom if ever it works.
-std::vector<Geom::Path>
-split_loopy_bezier (std::vector<Geom::Path> const & path_in){
-
-    std::vector<Geom::Path> ret; 
-    std::vector<Geom::Path>::const_iterator pi=path_in.begin();
-    for(; pi != path_in.end(); pi++) {
-        ret.push_back(Geom::Path());
-        for (Geom::Path::const_iterator curve(pi->begin()),end(pi->end()); curve != end; ++curve){
-
-            //is the current curve a cubic bezier?
-            if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const *>(&(*curve))){
-                Geom::CubicBezier theCurve = *cubic_bezier;
-                std::vector<Geom::Point> A = theCurve.points();
-
-                //is there a crossing in the polygon?
-                if( cross(A[2]-A[0],A[1]-A[0])*cross(A[3]-A[0],A[1]-A[0])<0 &&
-                    cross(A[0]-A[3],A[2]-A[3])*cross(A[1]-A[3],A[2]-A[3])<0){
-
-                    //split the curve where the tangent is parallel to the chord through end points.
-                    double a = cross(A[3]-A[0],A[1]-A[2]);
-                    double c = cross(A[3]-A[0],A[1]-A[0]);
-                    double t; //where to split; solution of 3*at^2-(a+c)t +c = 0. 
-                    //TODO: don't we have a clean deg 2 equation solver?...
-                    if (fabs(a)<.0001){
-                        t = .5;
+//---------------------------------------------------------------------------
+//LPEKnot specific Crossing Data manipulation.
+//---------------------------------------------------------------------------
+
+//Yet another crossing data representation.
+// an CrossingPoint stores
+//    -an intersection point
+//    -the involved path components
+//    -for each component, the time at which this crossing occurs + the order of this crossing along the component (when starting from 0).
+
+namespace LPEKnotNS {//just in case...
+CrossingPoints::CrossingPoints(std::vector<Geom::Path> const &paths) : std::vector<CrossingPoint>(){
+//    std::cout<<"\nCrossingPoints creation from path vector\n";
+    for( unsigned i=0; i<paths.size(); i++){
+        for( unsigned ii=0; ii < size_nondegenerate(paths[i]); ii++){
+            for( unsigned j=i; j<paths.size(); j++){
+                for( unsigned jj=(i==j?ii:0); jj < size_nondegenerate(paths[j]); jj++){
+                    std::vector<std::pair<double,double> > times;
+                    if ( i==j && ii==jj){
+
+//                         std::cout<<"--(self int)\n";
+//                         std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
+//                         std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
+
+                        find_self_intersections( times, paths[i][ii].toSBasis() );
                     }else{
-                        double delta = a*a-a*c+c*c;
-                        t = ((a+c)-sqrt(delta))/2/a;
-                        if ( t<0 || t>1 ) {t = ((a+c)+sqrt(delta))/2/a;}
+//                         std::cout<<"--(pair int)\n";
+//                         std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
+//                         std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
+//                         std::cout<<"with\n";
+//                         std::cout << paths[j][jj].toSBasis()[Geom::X] <<"\n";
+//                         std::cout << paths[j][jj].toSBasis()[Geom::Y] <<"\n";
+
+                        find_intersections( times, paths[i][ii].toSBasis(), paths[j][jj].toSBasis() );
+                    }
+                    for (unsigned k=0; k<times.size(); k++){
+                        //std::cout<<"intersection "<<i<<"["<<ii<<"]("<<times[k].first<<")= "<<j<<"["<<jj<<"]("<<times[k].second<<")\n";
+                        if (times[k].first == times[k].first && times[k].second == times[k].second ){//is this the way to test NaN?
+                            double zero = 1e-4;
+                            if ( i==j && fabs(times[k].first+ii - times[k].second-jj)<=zero ){//this is just end=start of successive curves in a path.
+                                continue;
+                            }
+                            if ( i==j && ii == 0 && jj == size_nondegenerate(paths[i])-1 &&
+                                 paths[i].closed() &&
+                                 fabs(times[k].first) <= zero && 
+                                 fabs(times[k].second - 1) <= zero ){//this is just end=start of a closed path.
+                                continue;
+                            }
+                            CrossingPoint cp;
+                            cp.pt = paths[i][ii].pointAt(times[k].first);
+                            cp.sign = 1;
+                            cp.i = i;
+                            cp.j = j;
+                            cp.ni = 0; cp.nj=0;//not set yet
+                            cp.ti = times[k].first + ii;
+                            cp.tj = times[k].second + jj;
+                            push_back(cp);
+                        }else{
+                            std::cout<<"ooops: find_(self)_intersections returned NaN:";
+                            //std::cout<<"intersection "<<i<<"["<<ii<<"](NaN)= "<<j<<"["<<jj<<"](NaN)\n";
+                        }
                     }
-                    //TODO: shouldn't Path have subdivide method?
-                    std::pair<Geom::BezierCurve<3>, Geom::BezierCurve<3> > splitCurve;
-                    splitCurve = theCurve.subdivide(t);
-                    ret.back().append(splitCurve.first);
-                    ret.back().append(splitCurve.second);
-
-                }else{//cubic bezier but no crossing.
-                    ret.back().append(*curve);
                 }
-            }else{//not cubic bezier.
-                ret.back().append(*curve);
             }
         }
     }
-    return ret;
+    for( unsigned i=0; i<paths.size(); i++){
+        std::map < double, unsigned > cuts;
+        for( unsigned k=0; k<size(); k++){
+            CrossingPoint cp = (*this)[k];
+            if (cp.i == i) cuts[cp.ti] = k;
+            if (cp.j == i) cuts[cp.tj] = k;
+        }
+        unsigned count = 0;
+        for ( std::map < double, unsigned >::iterator m=cuts.begin(); m!=cuts.end(); m++ ){
+            if ( (*this)[m->second].i == i && (*this)[m->second].ti == m->first ){
+                (*this)[m->second].ni = count;
+            }else{
+                (*this)[m->second].nj = count;
+            }
+            count++;
+        }
+    }
 }
 
+CrossingPoints::CrossingPoints(std::vector<double> const &input) : std::vector<CrossingPoint>()
+{
+    if (input.size()>0 && input.size()%9 ==0){
+        using namespace Geom;
+        for( unsigned n=0; n<input.size();  ){
+            CrossingPoint cp;
+            cp.pt[X] = input[n++];
+            cp.pt[Y] = input[n++];
+            cp.i = input[n++];
+            cp.j = input[n++];
+            cp.ni = input[n++];
+            cp.nj = input[n++];
+            cp.ti = input[n++];
+            cp.tj = input[n++];
+            cp.sign = input[n++];
+            push_back(cp);
+        }
+    }
+}
+
+std::vector<double>
+CrossingPoints::to_vector()
+{
+    using namespace Geom;
+    std::vector<double> result;
+    for( unsigned n=0; n<size(); n++){
+        CrossingPoint cp = (*this)[n];
+        result.push_back(cp.pt[X]);
+        result.push_back(cp.pt[Y]);
+        result.push_back(double(cp.i));
+        result.push_back(double(cp.j));
+        result.push_back(double(cp.ni));
+        result.push_back(double(cp.nj));
+        result.push_back(double(cp.ti));
+        result.push_back(double(cp.tj));
+        result.push_back(double(cp.sign));
+    }
+    return result;
+}
+
+//FIXME: rewrite to check success: return bool, put result in arg.
+CrossingPoint
+CrossingPoints::get(unsigned const i, unsigned const ni)
+{
+    for (unsigned k=0; k<size(); k++){
+        if (
+            ((*this)[k].i==i && (*this)[k].ni==ni) ||
+            ((*this)[k].j==i && (*this)[k].nj==ni)
+            ) return (*this)[k];
+    }
+    g_warning("LPEKnotNS::CrossingPoints::get error. %uth crossing along string %u not found.",ni,i);
+    assert(false);//debug purpose...
+    return CrossingPoint();
+}
+
+unsigned
+idx_of_nearest(CrossingPoints const &cpts, Geom::Point const &p)
+{
+    double dist=-1;
+    unsigned result = cpts.size();
+    for (unsigned k=0; k<cpts.size(); k++){
+        double dist_k = Geom::L2(p-cpts[k].pt);
+        if (dist<0 || dist>dist_k){
+            result = k;
+            dist = dist_k;
+        }
+    }
+    return result;
+}
+
+//TODO: Find a way to warn the user when the topology changes.
+//TODO: be smarter at guessing the signs when the topology changed?
+void
+CrossingPoints::inherit_signs(CrossingPoints const &other, int default_value)
+{
+    bool topo_changed = false;
+    for (unsigned n=0; n<size(); n++){
+        if ( n<other.size() &&
+             other[n].i  == (*this)[n].i  &&
+             other[n].j  == (*this)[n].j  &&
+             other[n].ni == (*this)[n].ni &&
+             other[n].nj == (*this)[n].nj    )
+        {
+            (*this)[n].sign = other[n].sign;
+        }else{
+            topo_changed = true;
+            break;
+        }
+    }
+    if (topo_changed){
+        //TODO: Find a way to warn the user!!
+//        std::cout<<"knot topolgy changed!\n";
+        for (unsigned n=0; n<size(); n++){
+            Geom::Point p = (*this)[n].pt;
+            unsigned idx = idx_of_nearest(other,p);
+            if (idx<other.size()){
+                (*this)[n].sign = other[idx].sign;
+            }else{
+                (*this)[n].sign = default_value;
+            }
+        }
+    }
+}
+
+}
+
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+//LPEKnot effect.
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+
+
+LPEKnot::LPEKnot(LivePathEffectObject *lpeobject) :
+    Effect(lpeobject),
+    // initialise your parameters here:
+    interruption_width(_("Fixed width:"), _("Size of hidden region of lower string"), "interruption_width", &wr, this, 3),
+    prop_to_stroke_width(_("In units of stroke width"), _("Consider 'Interruption width' as a ratio of stroke width"), "prop_to_stroke_width", &wr, this, true),
+    add_stroke_width(_("Stroke width"), _("Add the stroke width to the interruption size"), "add_stroke_width", &wr, this, true),
+    add_other_stroke_width(_("Crossing path stroke width"), _("Add crossed stroke width to the interruption size"), "add_other_stroke_width", &wr, this, true),
+    switcher_size(_("Switcher size:"), _("Orientation indicator/switcher size"), "switcher_size", &wr, this, 15),
+    crossing_points_vector(_("Crossing Signs"), _("Crossings signs"), "crossing_points_vector", &wr, this),
+    gpaths(),gstroke_widths()
+{
+    // register all your parameters here, so Inkscape knows which parameters this effect has:
+    registerParameter( dynamic_cast<Parameter *>(&interruption_width) );
+    registerParameter( dynamic_cast<Parameter *>(&prop_to_stroke_width) );
+    registerParameter( dynamic_cast<Parameter *>(&add_stroke_width) );
+    registerParameter( dynamic_cast<Parameter *>(&add_other_stroke_width) );
+    registerParameter( dynamic_cast<Parameter *>(&switcher_size) );
+    registerParameter( dynamic_cast<Parameter *>(&crossing_points_vector) );
+
+    registerKnotHolderHandle(new KnotHolderEntityCrossingSwitcher(), _("Drag to select a crossing, click to flip it"));
+    crossing_points = LPEKnotNS::CrossingPoints();
+    selectedCrossing = 0;
+    switcher = Geom::Point(0,0);
+}
+
+LPEKnot::~LPEKnot()
+{
+
+}
+
+void
+LPEKnot::updateSwitcher(){
+    if (selectedCrossing < crossing_points.size()){
+        switcher = crossing_points[selectedCrossing].pt;
+        //std::cout<<"placing switcher at "<<switcher<<" \n";
+    }else if (crossing_points.size()>0){
+        selectedCrossing = 0;
+        switcher = crossing_points[selectedCrossing].pt;
+        //std::cout<<"placing switcher at "<<switcher<<" \n";
+    }else{
+        //std::cout<<"hiding switcher!\n";
+        //TODO: is there a way to properly hide the helper.
+        //switcher = Geom::Point(Geom::infinity(),Geom::infinity());
+        switcher = Geom::Point(1e10,1e10);
+    }
+}
 
 std::vector<Geom::Path>
-LPEKnot::doEffect_path (std::vector<Geom::Path> const & input_path)
+LPEKnot::doEffect_path (std::vector<Geom::Path> const &path_in)
 {
     using namespace Geom;
     std::vector<Geom::Path> path_out;
-    double width = interruption_width;
 
-    std::vector<Geom::Path> path_in = split_loopy_bezier(input_path);
+    if (gpaths.size()==0){
+        return path_in;
+    }
+
+    for (unsigned comp=0; comp<path_in.size(); comp++){
+
+        //find the relevant path component in gpaths (required to allow groups!)
+        //Q: do we always recieve the group members in the same order? can we rest on that?
+        unsigned i0 = 0;
+        for (i0=0; i0<gpaths.size(); i0++){
+            if (path_in[comp]==gpaths[i0]) break;
+        }
+        if (i0 == gpaths.size() ) {THROW_EXCEPTION("lpe-knot error: group member not recognized");}// this should not happen...
 
-    CrossingSet crossingTable = crossings_among(path_in);
-    for (unsigned i = 0; i < crossingTable.size(); i++){
         std::vector<Interval> dom;
-        dom.push_back(Interval(0.,path_in.at(i).size()-0.00001));
-        //TODO: handle closed curves...
-        for (unsigned crs = 0; crs < crossingTable.at(i).size(); crs++){
-            Crossing crossing = crossingTable.at(i).at(crs);
-            unsigned j = crossing.getOther(i);
-            //TODO: select dir according to a parameter...
-            if ((crossing.dir and crossing.a==i) or (not crossing.dir and crossing.b==i) or (i==j)){
-                if (i==j and not crossing.dir) {
-                    double temp = crossing.ta;
-                    crossing.ta = crossing.tb; 
-                    crossing.tb = temp; 
-                    crossing.dir = not crossing.dir;
+        dom.push_back(Interval(0., size_nondegenerate(gpaths[i0])));
+        for (unsigned p = 0; p < crossing_points.size(); p++){
+            if (crossing_points[p].i == i0 || crossing_points[p].j == i0){
+                unsigned i = crossing_points[p].i;
+                unsigned j = crossing_points[p].j;
+                double ti = crossing_points[p].ti;
+                double tj = crossing_points[p].tj;
+                
+                double curveidx, t;
+                
+                t = modf(ti, &curveidx);
+                if(curveidx == size_nondegenerate(gpaths[i]) ) { curveidx--; t = 1.;}
+                assert(curveidx >= 0 && curveidx < size_nondegenerate(gpaths[i]));
+                std::vector<Point> flag_i = gpaths[i][curveidx].pointAndDerivatives(t,1);
+
+                t = modf(tj, &curveidx);
+                if(curveidx == size_nondegenerate(gpaths[j]) ) { curveidx--; t = 1.;}
+                assert(curveidx >= 0 && curveidx < size_nondegenerate(gpaths[j]));
+                std::vector<Point> flag_j = gpaths[j][curveidx].pointAndDerivatives(t,1);
+
+
+                int geom_sign = ( cross(flag_i[1],flag_j[1]) > 0 ? 1 : -1);
+
+                bool i0_is_under = false;
+                if ( crossing_points[p].sign * geom_sign > 0 ){
+                    i0_is_under = ( i == i0 );
+                }else if ( crossing_points[p].sign * geom_sign < 0 ){
+                    if (j == i0){
+                        std::swap( i, j);
+                        std::swap(ti, tj);
+                        std::swap(flag_i,flag_j);
+                        i0_is_under = true;
+                    }
+                }
+                if (i0_is_under){
+                    double width = interruption_width;
+                    if ( prop_to_stroke_width.get_value() ) {
+                        width *= gstroke_widths[i];
+                    }
+                    if ( add_stroke_width.get_value() ) {
+                        width += gstroke_widths[i];
+                    }
+                    if ( add_other_stroke_width.get_value() ) {
+                        width += gstroke_widths[j];
+                    }
+                    Interval hidden = findShadowedTime(gpaths[i0], flag_j, ti, width/2);
+                    double period  = size_nondegenerate(gpaths[i0]);
+                    if (hidden.max() > period ) hidden -= period;
+                    if (hidden.min()<0){
+                        dom = complementOf( Interval(0,hidden.max()) ,dom);
+                        dom = complementOf( Interval(hidden.min()+period, period) ,dom);
+                    }else{
+                        dom = complementOf(hidden,dom);
+                    }
                 }
-                Interval hidden = findShadowedTime(path_in.at(i),path_in.at(j),crossing,i,width);                
-                dom = complementOf(hidden,dom);
             }
         }
-        for (unsigned comp = 0; comp < dom.size(); comp++){
-            assert(dom.at(comp).min() >=0 and dom.at(comp).max() < path_in.at(i).size());
-            path_out.push_back(path_in.at(i).portion(dom.at(comp)));
+
+        //If the all component is hidden, continue.
+        if ( dom.size() == 0){
+            continue;
+        }
+
+        //If the current path is closed and the last/first point is still there, glue first and last piece.
+        unsigned beg_comp = 0, end_comp = dom.size();
+        if ( gpaths[i0].closed() && dom.front().min() == 0 && dom.back().max() ==  size_nondegenerate(gpaths[i0]) ){
+            if ( dom.size() == 1){
+                path_out.push_back(gpaths[i0]);
+                continue;
+            }else{
+//                std::cout<<"fusing first and last component\n";
+                beg_comp++;
+                end_comp--;
+                Path first = gpaths[i0].portion(dom.back());
+                //FIXME: STITCH_DISCONTINUOUS should not be necessary (?!?)
+                first.append(gpaths[i0].portion(dom.front()), Path::STITCH_DISCONTINUOUS);
+                path_out.push_back(first);
+            }
+        }
+        for (unsigned comp = beg_comp; comp < end_comp; comp++){
+            assert(dom.at(comp).min() >=0 and dom.at(comp).max() <= size_nondegenerate(gpaths.at(i0)));
+            path_out.push_back(gpaths[i0].portion(dom.at(comp)));
         }
-    }   
+    }
     return path_out;
 }
 
 
-/*
-Geom::Piecewise<Geom::D2<Geom::SBasis> >
-addLinearEnds (Geom::Piecewise<Geom::D2<Geom::SBasis> > & m){
+
+//recursively collect gpaths and stroke widths (stolen from "sp-lpe_item.cpp").
+void collectPathsAndWidths (SPLPEItem const *lpeitem, std::vector<Geom::Path> &paths, std::vector<double> &stroke_widths){
+    if (SP_IS_GROUP(lpeitem)) {
+        GSList const *item_list = sp_item_group_item_list(SP_GROUP(lpeitem));
+        for ( GSList const *iter = item_list; iter; iter = iter->next ) {
+            SPObject *subitem = static_cast<SPObject *>(iter->data);
+            if (SP_IS_LPE_ITEM(subitem)) {
+                collectPathsAndWidths(SP_LPE_ITEM(subitem), paths, stroke_widths);
+            }
+        }
+    }
+    else if (SP_IS_SHAPE(lpeitem)) {
+        SPCurve * c = NULL;
+        if (SP_IS_PATH(lpeitem)) {
+            c = sp_path_get_curve_for_edit(SP_PATH(lpeitem));
+        } else {
+            c = SP_SHAPE(lpeitem)->getCurve();
+        }
+        if (c) {
+            Geom::PathVector subpaths = c->get_pathvector();
+            for (unsigned i=0; i<subpaths.size(); i++){
+                paths.push_back(subpaths[i]);
+                //FIXME: do we have to be more carefull when trying to access stroke width?
+                stroke_widths.push_back(SP_ITEM(lpeitem)->style->stroke_width.computed);
+            }
+        }
+    }
+}
+
+
+void
+LPEKnot::doBeforeEffect (SPLPEItem *lpeitem)
+{
     using namespace Geom;
-    Piecewise<D2<SBasis> > output;
-    Piecewise<D2<SBasis> > start;
-    Piecewise<D2<SBasis> > end;
-    double x,y,vx,vy;
-
-    x  = m.segs.front()[0].at0();
-    y  = m.segs.front()[1].at0();
-    vx = m.segs.front()[0][1][0]+Tri(m.segs.front()[0][0]);
-    vy = m.segs.front()[1][1][0]+Tri(m.segs.front()[1][0]);
-    start = Piecewise<D2<SBasis> >(D2<SBasis>(Linear (x-vx,x),Linear (y-vy,y)));
-    start.offsetDomain(m.cuts.front()-1.);
-
-    x  = m.segs.back()[0].at1();
-    y  = m.segs.back()[1].at1();
-    vx = -m.segs.back()[0][1][1]+Tri(m.segs.back()[0][0]);;
-    vy = -m.segs.back()[1][1][1]+Tri(m.segs.back()[1][0]);;
-    end = Piecewise<D2<SBasis> >(D2<SBasis>(Linear (x,x+vx),Linear (y,y+vy)));
-    //end.offsetDomain(m.cuts.back());
-
-    output = start;
-    output.concat(m);
-    output.concat(end);
-    return output;
+    original_bbox(lpeitem);
+
+    gpaths = std::vector<Geom::Path>();
+    gstroke_widths = std::vector<double>();
+    collectPathsAndWidths(lpeitem, gpaths, gstroke_widths);
+
+//     std::cout<<"\nPaths on input:\n";
+//     for (unsigned i=0; i<gpaths.size(); i++){
+//         for (unsigned ii=0; ii<gpaths[i].size(); ii++){
+//             std::cout << gpaths[i][ii].toSBasis()[Geom::X] <<"\n";
+//             std::cout << gpaths[i][ii].toSBasis()[Geom::Y] <<"\n";
+//             std::cout<<"--\n";
+//         }
+//     }
+                        
+    //std::cout<<"crossing_pts_vect: "<<crossing_points_vector.param_getSVGValue()<<".\n";
+    //std::cout<<"prop_to_stroke_width: "<<prop_to_stroke_width.param_getSVGValue()<<".\n";
+
+    LPEKnotNS::CrossingPoints old_crdata(crossing_points_vector.data());
+
+//     std::cout<<"\nVectorParam size:"<<crossing_points_vector.data().size()<<"\n";
+
+//     std::cout<<"\nOld crdata ("<<old_crdata.size()<<"): \n";
+//     for (unsigned toto=0; toto<old_crdata.size(); toto++){
+//         std::cout<<"(";
+//         std::cout<<old_crdata[toto].i<<",";
+//         std::cout<<old_crdata[toto].j<<",";
+//         std::cout<<old_crdata[toto].ni<<",";
+//         std::cout<<old_crdata[toto].nj<<",";
+//         std::cout<<old_crdata[toto].ti<<",";
+//         std::cout<<old_crdata[toto].tj<<",";
+//         std::cout<<old_crdata[toto].sign<<"),";
+//     }
+
+    //if ( old_crdata.size() > 0 ) std::cout<<"first crossing sign = "<<old_crdata[0].sign<<".\n";
+    //else std::cout<<"old data is empty!!\n";
+    crossing_points = LPEKnotNS::CrossingPoints(gpaths);
+//     std::cout<<"\nNew crdata ("<<crossing_points.size()<<"): \n";
+//     for (unsigned toto=0; toto<crossing_points.size(); toto++){
+//         std::cout<<"(";
+//         std::cout<<crossing_points[toto].i<<",";
+//         std::cout<<crossing_points[toto].j<<",";
+//         std::cout<<crossing_points[toto].ni<<",";
+//         std::cout<<crossing_points[toto].nj<<",";
+//         std::cout<<crossing_points[toto].ti<<",";
+//         std::cout<<crossing_points[toto].tj<<",";
+//         std::cout<<crossing_points[toto].sign<<"),";
+//     }
+    crossing_points.inherit_signs(old_crdata);
+    crossing_points_vector.param_set_and_write_new_value(crossing_points.to_vector());
+    updateSwitcher();
+}
+
+
+static LPEKnot *
+get_effect(SPItem *item)
+{
+    Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
+    if (effect->effectType() != KNOT) {
+        g_print ("Warning: Effect is not of type LPEKnot!\n");
+        return NULL;
+    }
+    return static_cast<LPEKnot *>(effect);
 }
 
-Geom::Piecewise<Geom::D2<Geom::SBasis> >
-LPEKnot::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in)
+void
+LPEKnot::addCanvasIndicators(SPLPEItem */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
 {
     using namespace Geom;
+    double r = switcher_size*.1;
+    char const * svgd;
+    //TODO: use a nice path!
+    if (selectedCrossing >= crossing_points.size()||crossing_points[selectedCrossing].sign > 0){
+        //svgd = "M -10,0 A 10 10 0 1 0 0,-10 l  5,-1 -1,2";
+        svgd = "m -7.07,7.07 c 3.9,3.91 10.24,3.91 14.14,0 3.91,-3.9 3.91,-10.24 0,-14.14 -3.9,-3.91 -10.24,-3.91 -14.14,0 l 2.83,-4.24 0.7,2.12";
+    }else if (crossing_points[selectedCrossing].sign < 0){
+        //svgd = "M  10,0 A 10 10 0 1 1 0,-10 l -5,-1  1,2";
+        svgd = "m 7.07,7.07 c -3.9,3.91 -10.24,3.91 -14.14,0 -3.91,-3.9 -3.91,-10.24 0,-14.14 3.9,-3.91 10.24,-3.91 14.14,0 l -2.83,-4.24 -0.7,2.12";
+    }else{
+        //svgd = "M 10,0 A 10 10 0 1 0 -10,0 A 10 10 0 1 0 10,0 ";
+        svgd = "M 10,0 C 10,5.52 5.52,10 0,10 -5.52,10 -10,5.52 -10,0 c 0,-5.52 4.48,-10 10,-10 5.52,0 10,4.48 10,10 z";
+    }
+    PathVector pathv = sp_svg_read_pathv(svgd);
+    pathv *= Matrix(r,0,0,r,0,0);
+    pathv+=switcher;
+    hp_vec.push_back(pathv);
+}
+
+void
+KnotHolderEntityCrossingSwitcher::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
+{
+    LPEKnot* lpe = get_effect(item);
 
+    lpe->selectedCrossing = idx_of_nearest(lpe->crossing_points,p);
+    lpe->updateSwitcher();
+    // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
+    sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
+}
 
-    Piecewise<D2<SBasis> > output;
-    Piecewise<D2<SBasis> > m = addLinearEnds(pwd2_in);
-
-    Piecewise<D2<SBasis> > v = derivative(pwd2_in);
-    Piecewise<D2<SBasis> > n = unitVector(v);
-
-// // -------- Pleins et delies vs courbure ou direction...
-//     Piecewise<D2<SBasis> > a = derivative(v);
-//     Piecewise<SBasis> a_cross_n = cross(a,n);
-//     Piecewise<SBasis> v_dot_n = dot(v,n);
-//     //Piecewise<D2<SBasis> > rfrac = sectionize(D2<Piecewise<SBasis> >(a_cross_n,v_dot_n));
-//     //Piecewise<SBasis> h = atan2(rfrac)*interruption_width;
-//     Piecewise<SBasis> h = reciprocal(curvature(pwd2_in))*interruption_width;
-//
-// //    Piecewise<D2<SBasis> > dir = Piecewise<D2<SBasis> >(D2<SBasis>(Linear(0),Linear(-1)));
-// //    Piecewise<SBasis> h = dot(n,dir)+1.;
-// //    h *= h*(interruption_width/4.);
-//
-//     n = rot90(n);
-//     output = pwd2_in+h*n;
-//     output.concat(pwd2_in-h*n);
-//
-// //-----------
-
-    //output.concat(m);
-    return output;
+Geom::Point
+KnotHolderEntityCrossingSwitcher::knot_get()
+{
+    LPEKnot* lpe = get_effect(item);
+    return snap_knot_position(lpe->switcher);
 }
-*/
+
+void
+KnotHolderEntityCrossingSwitcher::knot_click(guint state)
+{
+    LPEKnot* lpe = get_effect(item);
+    unsigned s = lpe->selectedCrossing;
+    if (s < lpe->crossing_points.size()){
+        if (state & GDK_SHIFT_MASK){
+            lpe->crossing_points[s].sign = 1;
+        }else{
+            int sign = lpe->crossing_points[s].sign;
+            lpe->crossing_points[s].sign = ((sign+2)%3)-1;
+            //std::cout<<"crossing set to"<<lpe->crossing_points[s].sign<<".\n";
+        }
+        lpe->crossing_points_vector.param_set_and_write_new_value(lpe->crossing_points.to_vector());
+        DocumentUndo::done(lpe->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, /// @todo Is this the right verb?
+                           _("Change knot crossing"));
+
+        // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
+//        sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
+    }
+}
+
 
 /* ######################## */
 
-} //namespace LivePathEffect (setq default-directory "c:/Documents And Settings/jf/Mes Documents/InkscapeSVN")
-} /* namespace Inkscape */
+} // namespace LivePathEffect
+} // namespace Inkscape
 
 /*
   Local Variables:
@@ -256,4 +671,5 @@ LPEKnot::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in)
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
+