Code

UI fixes (a.o. Bug #560751 )
[inkscape.git] / src / live_effects / lpe-patternalongpath.cpp
index 9b28192192a4edf4bd9ecc08f3acd163e7c34989..36f257333260fba589ac676a7d323cabea219e9d 100644 (file)
@@ -7,10 +7,9 @@
  */
 
 #include "live_effects/lpe-patternalongpath.h"
+#include "live_effects/lpeobject.h"
 #include "sp-shape.h"
 #include "display/curve.h"
-#include <libnr/n-art-bpath.h>
-#include "live_effects/n-art-bpath-2geom.h"
 #include "svg/svg.h"
 #include "ui/widget/scalar.h"
 
@@ -34,7 +33,7 @@ B is a map t --> B(t) = ( a(t), b(t) ).
 The first step is to re-parametrize B by its arc length: this is the parametrization in which a point p on B is located by its distance s from start. One obtains a new map s --> U(s) = (a'(s),b'(s)), that still describes the same path B, but where the distance along B from start to
 U(s) is s itself.
 
-We also need a unit normal to the path. This can be obtained by computing a unit tangent vector, and rotate it by 90°. Call this normal vector N(s).
+We also need a unit normal to the path. This can be obtained by computing a unit tangent vector, and rotate it by 90. Call this normal vector N(s).
 
 The basic deformation associated to B is then given by:
 
@@ -59,15 +58,26 @@ static const Util::EnumDataConverter<PAPCopyType> PAPCopyTypeConverter(PAPCopyTy
 
 LPEPatternAlongPath::LPEPatternAlongPath(LivePathEffectObject *lpeobject) :
     Effect(lpeobject),
-    pattern(_("Pattern source"), _("Path to put along the skeleton path"), "pattern", &wr, this, "M0,0 L1,0"),
-    copytype(_("Pattern copies"), _("How many pattern copies to place along the skeleton path"), "copytype", PAPCopyTypeConverter, &wr, this, PAPCT_SINGLE_STRETCHED),
-    prop_scale(_("Width"), _("Width of the pattern"), "prop_scale", &wr, this, 1),
-    scale_y_rel(_("Width in units of length"), _("Scale the width of the pattern in units of its length"), "scale_y_rel", &wr, this, false),
-    spacing(_("Spacing"), _("Space between copies of the pattern. Negative values allowed, but are limited to -90% of pattern width."), "spacing", &wr, this, 0),
-    normal_offset(_("Normal offset"), "", "normal_offset", &wr, this, 0),
-    tang_offset(_("Tangential offset"), "", "tang_offset", &wr, this, 0),
-    prop_units(_("Offsets in unit of pattern size"), "Spacing, tangential and normal offset are expressed as a ratio of width/height", "prop_units", &wr, this, false),
-    vertical_pattern(_("Pattern is vertical"), "Rotate pattern 90 deg before applying", "vertical_pattern", &wr, this, false)
+    pattern(_("Pattern source:"), _("Path to put along the skeleton path"), "pattern", &wr, this, "M0,0 L1,0"),
+    copytype(_("Pattern copies:"), _("How many pattern copies to place along the skeleton path"),
+        "copytype", PAPCopyTypeConverter, &wr, this, PAPCT_SINGLE_STRETCHED),
+    prop_scale(_("Width:"), _("Width of the pattern"), "prop_scale", &wr, this, 1),
+    scale_y_rel(_("Width in units of length"),
+        _("Scale the width of the pattern in units of its length"),
+        "scale_y_rel", &wr, this, false),
+    spacing(_("Spacing:"),
+        // xgettext:no-c-format
+        _("Space between copies of the pattern. Negative values allowed, but are limited to -90% of pattern width."),
+        "spacing", &wr, this, 0),
+    normal_offset(_("Normal offset:"), "", "normal_offset", &wr, this, 0),
+    tang_offset(_("Tangential offset:"), "", "tang_offset", &wr, this, 0),
+    prop_units(_("Offsets in unit of pattern size"),
+        _("Spacing, tangential and normal offset are expressed as a ratio of width/height"),
+        "prop_units", &wr, this, false),
+    vertical_pattern(_("Pattern is vertical"), _("Rotate pattern 90 deg before applying"),
+        "vertical_pattern", &wr, this, false),
+    fuse_tolerance(_("Fuse nearby ends:"), _("Fuse ends closer than this number. 0 means don't fuse."),
+        "fuse_tolerance", &wr, this, 0)
 {
     registerParameter( dynamic_cast<Parameter *>(&pattern) );
     registerParameter( dynamic_cast<Parameter *>(&copytype) );
@@ -78,6 +88,7 @@ LPEPatternAlongPath::LPEPatternAlongPath(LivePathEffectObject *lpeobject) :
     registerParameter( dynamic_cast<Parameter *>(&tang_offset) );
     registerParameter( dynamic_cast<Parameter *>(&prop_units) );
     registerParameter( dynamic_cast<Parameter *>(&vertical_pattern) );
+    registerParameter( dynamic_cast<Parameter *>(&fuse_tolerance) );
 
     prop_scale.param_set_digits(3);
     prop_scale.param_set_increments(0.01, 0.10);
@@ -88,146 +99,146 @@ LPEPatternAlongPath::~LPEPatternAlongPath()
 
 }
 
-
-//TODO: does this already exist in 2Geom? if not, move this there...
-static
-std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > 
-split_at_discontinuities (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwsbin, double tol = .0001)
-{
-    using namespace Geom;
-    std::vector<Piecewise<D2<SBasis> > > ret;
-    unsigned piece_start = 0;
-    for (unsigned i=0; i<pwsbin.segs.size(); i++){
-        if (i==(pwsbin.segs.size()-1) || L2(pwsbin.segs[i].at1()- pwsbin.segs[i+1].at0()) > tol){
-            Piecewise<D2<SBasis> > piece;
-            piece.cuts.push_back(pwsbin.cuts[piece_start]);
-            for (unsigned j = piece_start; j<i+1; j++){
-                piece.segs.push_back(pwsbin.segs[j]);
-                piece.cuts.push_back(pwsbin.cuts[j+1]);                
-            }
-            ret.push_back(piece);
-            piece_start = i+1;
-        }
-    }
-    return ret;
-}
-
-
 Geom::Piecewise<Geom::D2<Geom::SBasis> >
-LPEPatternAlongPath::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in)
+LPEPatternAlongPath::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
 {
     using namespace Geom;
 
+    // Don't allow empty path parameter:
+    if ( pattern.get_pathvector().empty() ) {
+        return pwd2_in;
+    }
+
 /* Much credit should go to jfb and mgsloan of lib2geom development for the code below! */
     Piecewise<D2<SBasis> > output;
+    std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > pre_output;
 
     PAPCopyType type = copytype.get_value();
 
-    D2<Piecewise<SBasis> > patternd2 = make_cuts_independant(pattern.get_pwd2());
+    D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pattern.get_pwd2());
     Piecewise<SBasis> x0 = vertical_pattern.get_value() ? Piecewise<SBasis>(patternd2[1]) : Piecewise<SBasis>(patternd2[0]);
     Piecewise<SBasis> y0 = vertical_pattern.get_value() ? Piecewise<SBasis>(patternd2[0]) : Piecewise<SBasis>(patternd2[1]);
-    Interval pattBndsX = bounds_exact(x0);
-    x0 -= pattBndsX.min();
-    Interval pattBndsY = bounds_exact(y0);
-    y0 -= pattBndsY.middle();
-
-    double xspace  = spacing;
-    double noffset = normal_offset;
-    double toffset = tang_offset;
-    if (prop_units.get_value()){
-        xspace  *= pattBndsX.extent();
-        noffset *= pattBndsY.extent();
-        toffset *= pattBndsX.extent();
-    }
+    OptInterval pattBndsX = bounds_exact(x0);
+    OptInterval pattBndsY = bounds_exact(y0);
+    if (pattBndsX && pattBndsY) {
+        x0 -= pattBndsX->min();
+        y0 -= pattBndsY->middle();
+
+        double xspace  = spacing;
+        double noffset = normal_offset;
+        double toffset = tang_offset;
+        if (prop_units.get_value() && pattBndsY){
+            xspace  *= pattBndsX->extent();
+            noffset *= pattBndsY->extent();
+            toffset *= pattBndsX->extent();
+        }
 
-    //Prevent more than 90% overlap...
-    if (xspace < -pattBndsX.extent()*.9) {
-        xspace = -pattBndsX.extent()*.9;
-    }
-    //TODO: dynamical update of parameter ranges?
-    //if (prop_units.get_value()){
-    //        spacing.param_set_range(-.9, NR_HUGE);
-    //    }else{
-    //        spacing.param_set_range(-pattBndsX.extent()*.9, NR_HUGE);
-    //    }
-
-    y0+=noffset;
-
-    std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > paths_in;
-    paths_in = split_at_discontinuities(pwd2_in);
-
-    for (unsigned idx = 0; idx < paths_in.size(); idx++){
-        Geom::Piecewise<Geom::D2<Geom::SBasis> > path_i = paths_in[idx];
-        Piecewise<SBasis> x = x0;
-        Piecewise<SBasis> y = y0;
-        Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(path_i,2,.1);
-        uskeleton = remove_short_cuts(uskeleton,.01);
-        Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
-        n = force_continuity(remove_short_cuts(n,.1));
-        
-        int nbCopies = 0;
-        double scaling = 1;
-        switch(type) {
-            case PAPCT_REPEATED:
-                nbCopies = floor((uskeleton.domain().extent() - toffset + xspace)/(pattBndsX.extent()+xspace));
-                pattBndsX = Interval(pattBndsX.min(),pattBndsX.max()+xspace);
-                break;
-                
-            case PAPCT_SINGLE:
-                nbCopies = (toffset + pattBndsX.extent() < uskeleton.domain().extent()) ? 1 : 0;
-                break;
-                
-            case PAPCT_SINGLE_STRETCHED:
-                nbCopies = 1;
-                scaling = (uskeleton.domain().extent() - toffset)/pattBndsX.extent();
-                break;
-                
-            case PAPCT_REPEATED_STRETCHED:
-                // if uskeleton is closed:
-                if(path_i.segs.front().at0() == path_i.segs.back().at1()){
-                    nbCopies = std::floor((uskeleton.domain().extent() - toffset)/(pattBndsX.extent()+xspace));
-                    pattBndsX = Interval(pattBndsX.min(),pattBndsX.max()+xspace);
-                    scaling = (uskeleton.domain().extent() - toffset)/(((double)nbCopies)*pattBndsX.extent());
-                    // if not closed: no space at the end
+        //Prevent more than 90% overlap...
+        if (xspace < -pattBndsX->extent()*.9) {
+            xspace = -pattBndsX->extent()*.9;
+        }
+        //TODO: dynamical update of parameter ranges?
+        //if (prop_units.get_value()){
+        //        spacing.param_set_range(-.9, NR_HUGE);
+        //    }else{
+        //        spacing.param_set_range(-pattBndsX.extent()*.9, NR_HUGE);
+        //    }
+
+        y0+=noffset;
+
+        std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > paths_in;
+        paths_in = split_at_discontinuities(pwd2_in);
+
+        for (unsigned idx = 0; idx < paths_in.size(); idx++){
+            Geom::Piecewise<Geom::D2<Geom::SBasis> > path_i = paths_in[idx];
+            Piecewise<SBasis> x = x0;
+            Piecewise<SBasis> y = y0;
+            Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(path_i,2,.1);
+            uskeleton = remove_short_cuts(uskeleton,.01);
+            Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
+            n = force_continuity(remove_short_cuts(n,.1));
+            
+            int nbCopies = 0;
+            double scaling = 1;
+            switch(type) {
+                case PAPCT_REPEATED:
+                    nbCopies = static_cast<int>(floor((uskeleton.domain().extent() - toffset + xspace)/(pattBndsX->extent()+xspace)));
+                    pattBndsX = Interval(pattBndsX->min(),pattBndsX->max()+xspace);
+                    break;
+                    
+                case PAPCT_SINGLE:
+                    nbCopies = (toffset + pattBndsX->extent() < uskeleton.domain().extent()) ? 1 : 0;
+                    break;
+                    
+                case PAPCT_SINGLE_STRETCHED:
+                    nbCopies = 1;
+                    scaling = (uskeleton.domain().extent() - toffset)/pattBndsX->extent();
+                    break;
+                    
+                case PAPCT_REPEATED_STRETCHED:
+                    // if uskeleton is closed:
+                    if(path_i.segs.front().at0() == path_i.segs.back().at1()){
+                        nbCopies = static_cast<int>(std::floor((uskeleton.domain().extent() - toffset)/(pattBndsX->extent()+xspace)));
+                        pattBndsX = Interval(pattBndsX->min(),pattBndsX->max()+xspace);
+                        scaling = (uskeleton.domain().extent() - toffset)/(((double)nbCopies)*pattBndsX->extent());
+                        // if not closed: no space at the end
+                    }else{
+                        nbCopies = static_cast<int>(std::floor((uskeleton.domain().extent() - toffset + xspace)/(pattBndsX->extent()+xspace)));
+                        pattBndsX = Interval(pattBndsX->min(),pattBndsX->max()+xspace);
+                        scaling = (uskeleton.domain().extent() - toffset)/(((double)nbCopies)*pattBndsX->extent() - xspace);
+                    }
+                    break;
+                    
+                default:
+                    return pwd2_in;
+            };
+            
+            double pattWidth = pattBndsX->extent() * scaling;
+            
+            if (scaling != 1.0) {
+                x*=scaling;
+            }
+            if ( scale_y_rel.get_value() ) {
+                y*=(scaling*prop_scale);
+            } else {
+                if (prop_scale != 1.0) y *= prop_scale;
+            }
+            x += toffset;
+            
+            double offs = 0;
+            for (int i=0; i<nbCopies; i++){
+                if (fuse_tolerance > 0){        
+                    Geom::Piecewise<Geom::D2<Geom::SBasis> > output_piece = compose(uskeleton,x+offs)+y*compose(n,x+offs);
+                    std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > splited_output_piece = split_at_discontinuities(output_piece);
+                    pre_output.insert(pre_output.end(), splited_output_piece.begin(), splited_output_piece.end() );
                 }else{
-                    nbCopies = std::floor((uskeleton.domain().extent() - toffset + xspace)/(pattBndsX.extent()+xspace));
-                    pattBndsX = Interval(pattBndsX.min(),pattBndsX.max()+xspace);
-                    scaling = (uskeleton.domain().extent() - toffset)/(((double)nbCopies)*pattBndsX.extent() - xspace);
+                    output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
                 }
-                break;
-                
-            default:
-                return pwd2_in;
-        };
-        
-        double pattWidth = pattBndsX.extent() * scaling;
-        
-        if (scaling != 1.0) {
-            x*=scaling;
-        }
-        if ( scale_y_rel.get_value() ) {
-            y*=(scaling*prop_scale);
-        } else {
-            if (prop_scale != 1.0) y *= prop_scale;
+                offs+=pattWidth;
+            }
         }
-        x += toffset;
-        
-        double offs = 0;
-        for (int i=0; i<nbCopies; i++){
-            output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
-            offs+=pattWidth;
+        if (fuse_tolerance > 0){        
+            pre_output = fuse_nearby_ends(pre_output, fuse_tolerance);
+            for (unsigned i=0; i<pre_output.size(); i++){
+                output.concat(pre_output[i]);
+            }
         }
+        return output;
+    } else {
+        return pwd2_in;
     }
-    return output;
 }
 
 void
 LPEPatternAlongPath::transform_multiply(Geom::Matrix const& postmul, bool set)
 {
-    // TODO: implement correct transformation instead of this default behavior
-    Effect::transform_multiply(postmul, set);
-}
+    // overriding the Effect class default method, disabling transform forwarding to the parameters.
 
+    // only take translations into account
+    if (postmul.isTranslation()) {
+        pattern.param_transform_multiply(postmul, set);
+    }
+}
 
 } // namespace LivePathEffect
 } /* namespace Inkscape */