Code

Tablet auto-organization and naming.
[inkscape.git] / src / live_effects / lpe-powerstroke.cpp
index cb45e0518faba0f4d4052534c44fc070ce680444..6109ea498f77ea546af3c4be4faf59e353ad4b44 100644 (file)
 #include <2geom/path.h>
 #include <2geom/piecewise.h>
 #include <2geom/sbasis-geometric.h>
-#include <2geom/svg-elliptical-arc.h>
 #include <2geom/transforms.h>
+#include <2geom/bezier-utils.h>
+
+
+/// @TODO  Move this to 2geom
+namespace Geom {
+namespace Interpolate {
+
+class Interpolator {
+public:
+    Interpolator() {};
+    virtual ~Interpolator() {};
+
+//    virtual Piecewise<D2<SBasis> > interpolateToPwD2Sb(std::vector<Point> points) = 0;
+    virtual Path interpolateToPath(std::vector<Point> points) = 0;
+
+private:
+    Interpolator(const Interpolator&);
+    Interpolator& operator=(const Interpolator&);
+};
+
+class Linear : public Interpolator {
+public:
+    Linear() {};
+    virtual ~Linear() {};
+
+    virtual Path interpolateToPath(std::vector<Point> points) {
+        Path path;
+        path.start( points.at(0) );
+        for (unsigned int i = 1 ; i < points.size(); ++i) {
+            path.appendNew<Geom::LineSegment>(points.at(i));
+        }
+        return path;
+    };
+
+private:
+    Linear(const Linear&);
+    Linear& operator=(const Linear&);
+};
+
+// this class is terrible
+class CubicBezierFit : public Interpolator {
+public:
+    CubicBezierFit() {};
+    virtual ~CubicBezierFit() {};
+
+    virtual Path interpolateToPath(std::vector<Point> points) {
+        unsigned int n_points = points.size();
+        // worst case gives us 2 segment per point
+        int max_segs = 8*n_points;
+        Geom::Point * b = g_new(Geom::Point, max_segs);
+        Geom::Point * points_array = g_new(Geom::Point, 4*n_points);
+        for (unsigned i = 0; i < n_points; ++i) {
+            points_array[i] = points.at(i);
+        }
+
+        double tolerance_sq = 0; // this value is just a random guess
+
+        int const n_segs = Geom::bezier_fit_cubic_r(b, points_array, n_points,
+                                                 tolerance_sq, max_segs);
+
+        Geom::Path fit;
+        if ( n_segs > 0)
+        {
+            fit.start(b[0]);
+            for (int c = 0; c < n_segs; c++) {
+                fit.appendNew<Geom::CubicBezier>(b[4*c+1], b[4*c+2], b[4*c+3]);
+            }
+        }
+        g_free(b);
+        g_free(points_array);
+        return fit;
+    };
+
+private:
+    CubicBezierFit(const CubicBezierFit&);
+    CubicBezierFit& operator=(const CubicBezierFit&);
+};
+
+/// @todo invent name for this class
+class CubicBezierJohan : public Interpolator {
+public:
+    CubicBezierJohan() {};
+    virtual ~CubicBezierJohan() {};
+
+    virtual Path interpolateToPath(std::vector<Point> points) {
+        Path fit;
+        fit.start(points.at(0));
+        for (unsigned int i = 1; i < points.size(); ++i) {
+            Point p0 = points.at(i-1);
+            Point p1 = points.at(i);
+            Point dx = Point(p1[X] - p0[X], 0);
+            fit.appendNew<CubicBezier>(p0+0.2*dx, p1-0.2*dx, p1);
+        }
+        return fit;
+    };
+
+private:
+    CubicBezierJohan(const CubicBezierJohan&);
+    CubicBezierJohan& operator=(const CubicBezierJohan&);
+};
+
+} //namespace Interpolate
+} //namespace Geom
 
 namespace Inkscape {
 namespace LivePathEffect {
@@ -31,6 +133,8 @@ LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) :
 {
     show_orig_path = true;
 
+    /// @todo offset_points are initialized with empty path, is that bug-save?
+
     registerParameter( dynamic_cast<Parameter *>(&offset_points) );
     registerParameter( dynamic_cast<Parameter *>(&sort_points) );
 }
@@ -45,26 +149,16 @@ void
 LPEPowerStroke::doOnApply(SPLPEItem *lpeitem)
 {
     std::vector<Geom::Point> points;
-    points.push_back( *(SP_SHAPE(lpeitem)->curve->first_point()) );
-    Geom::Path const *path = SP_SHAPE(lpeitem)->curve->first_path();
-    points.push_back( path->pointAt(path->size()/2) );
-    points.push_back( *(SP_SHAPE(lpeitem)->curve->last_point()) );
+    Geom::Path::size_type size = SP_SHAPE(lpeitem)->curve->get_pathvector().front().size_open();
+    points.push_back( Geom::Point(0,0) );
+    points.push_back( Geom::Point(0.5*size,0) );
+    points.push_back( Geom::Point(size,0) );
     offset_points.param_set_and_write_new_value(points);
 }
 
-static void append_half_circle(Geom::Piecewise<Geom::D2<Geom::SBasis> > &pwd2,
-                               Geom::Point const center, Geom::Point const &dir) {
-    using namespace Geom;
-
-    double r = L2(dir);
-    SVGEllipticalArc cap(center + dir, r, r, angle_between(Point(1,0), dir), false, false, center - dir);
-    Piecewise<D2<SBasis> > cap_pwd2(cap.toSBasis());
-    pwd2.continuousConcat(cap_pwd2);
-}
-
 static bool compare_offsets (Geom::Point first, Geom::Point second)
 {
-    return first[Geom::X] <= second[Geom::X];
+    return first[Geom::X] < second[Geom::X];
 }
 
 
@@ -73,44 +167,81 @@ LPEPowerStroke::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const &
 {
     using namespace Geom;
 
-    // perhaps use std::list instead of std::vector?
-    std::vector<Geom::Point> ts(offset_points.data().size());
+    offset_points.set_pwd2(pwd2_in);
 
-    for (unsigned int i; i < ts.size(); ++i) {
-        double t = nearest_point(offset_points.data().at(i), pwd2_in);
-        double offset = L2(pwd2_in.valueAt(t) - offset_points.data().at(i));
-        ts.at(i) = Geom::Point(t, offset);
-    }
-    if (sort_points) {
-        sort(ts.begin(), ts.end(), compare_offsets);
-    }
+    Piecewise<D2<SBasis> > der = unitVector(derivative(pwd2_in));
+    Piecewise<D2<SBasis> > n = rot90(der);
+    offset_points.set_pwd2_normal(n);
 
-    // create stroke path where points (x,y) = (t, offset)
-    Path strokepath;
-    strokepath.start( Point(pwd2_in.domain().min(),0) );
-    for (unsigned int i = 0 ; i < ts.size(); ++i) {
-        strokepath.appendNew<Geom::LineSegment>(ts.at(i));
+        // see if we should treat the path as being closed.
+    bool closed_path = false;
+    if ( are_near(pwd2_in.firstValue(), pwd2_in.lastValue()) ) {
+        closed_path = true;
     }
-    strokepath.appendNew<Geom::LineSegment>( Point(pwd2_in.domain().max(), 0) );
-    for (unsigned int i = 0; i < ts.size(); ++i) {
-        Geom::Point temp = ts.at(ts.size() - 1 - i);
-        strokepath.appendNew<Geom::LineSegment>( Geom::Point(temp[X], - temp[Y]) );
-    }
-    strokepath.close();
 
-    D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
-    Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
-    Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
+    Piecewise<D2<SBasis> > output;
+    if (!closed_path) {
+        // perhaps use std::list instead of std::vector?
+        std::vector<Geom::Point> ts(offset_points.data().size() + 2);
+        // first and last point coincide with input path (for now at least)
+        ts.front() = Point(pwd2_in.domain().min(),0);
+        ts.back()  = Point(pwd2_in.domain().max(),0);
+        for (unsigned int i = 0; i < offset_points.data().size(); ++i) {
+            ts.at(i+1) = offset_points.data().at(i);
+        }
 
-    Piecewise<D2<SBasis> > der = unitVector(derivative(pwd2_in));
-    Piecewise<D2<SBasis> > n   = rot90(der);
+        if (sort_points) {
+            sort(ts.begin(), ts.end(), compare_offsets);
+        }
+
+        // create stroke path where points (x,y) := (t, offset)
+        Geom::Interpolate::CubicBezierJohan interpolator;
+        Path strokepath = interpolator.interpolateToPath(ts);
+        Path mirroredpath = strokepath.reverse() * Geom::Scale(1,-1);
+
+        strokepath.append(mirroredpath, Geom::Path::STITCH_DISCONTINUOUS);
+        strokepath.close();
 
-//    output  = pwd2_in + n * offset;
-//    append_half_circle(output, pwd2_in.lastValue(), n.lastValue() * offset);
-//    output.continuousConcat(reverse(pwd2_in - n * offset));
-//    append_half_circle(output, pwd2_in.firstValue(), -n.firstValue() * offset);
+        D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
+        Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
+        Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
+
+        output = compose(pwd2_in,x) + y*compose(n,x);
+    } else {
+        // path is closed
+
+        // perhaps use std::list instead of std::vector?
+        std::vector<Geom::Point> ts = offset_points.data();
+        if (sort_points) {
+            sort(ts.begin(), ts.end(), compare_offsets);
+        }
+        // add extra points for interpolation between first and last point
+        Point first_point = ts.front();
+        Point last_point = ts.back();
+        ts.insert(ts.begin(), last_point - Point(pwd2_in.domain().extent() ,0));
+        ts.push_back( first_point + Point(pwd2_in.domain().extent() ,0) );
+        // create stroke path where points (x,y) := (t, offset)
+        Geom::Interpolate::CubicBezierJohan interpolator;
+        Path strokepath = interpolator.interpolateToPath(ts);
+
+        // output 2 separate paths
+        D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
+        Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
+        Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
+        // find time values for which x lies outside path domain
+        // and only take portion of x and y that lies within those time values
+        std::vector< double > rtsmin = roots (x - pwd2_in.domain().min());
+        std::vector< double > rtsmax = roots (x - pwd2_in.domain().max());
+        if ( !rtsmin.empty() && !rtsmax.empty() ) {
+            x = portion(x, rtsmin.at(0), rtsmax.at(0));
+            y = portion(y, rtsmin.at(0), rtsmax.at(0));
+        }
+        output = compose(pwd2_in,x) + y*compose(n,x);
+        x = reverse(x);
+        y = reverse(y);
+        output.concat(compose(pwd2_in,x) - y*compose(n,x));
+    }
 
-    Piecewise<D2<SBasis> > output = compose(pwd2_in,x) + y*compose(n,x);
     return output;
 }