Code

powerstroke: add visually 'nice' interpolator, good enough for now
[inkscape.git] / src / live_effects / lpe-powerstroke.cpp
1 #define INKSCAPE_LPE_POWERSTROKE_CPP
2 /** \file
3  * @brief  PowerStroke LPE implementation. Creates curves with modifiable stroke width.
4  */
5 /* Authors:
6  *   Johan Engelen <j.b.c.engelen@utwente.nl>
7  *
8  * Copyright (C) 2010 Authors
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
13 #include "live_effects/lpe-powerstroke.h"
15 #include "sp-shape.h"
16 #include "display/curve.h"
18 #include <2geom/path.h>
19 #include <2geom/piecewise.h>
20 #include <2geom/sbasis-geometric.h>
21 #include <2geom/transforms.h>
22 #include <2geom/bezier-utils.h>
25 /// @TODO  Move this to 2geom
26 namespace Geom {
27 namespace Interpolate {
29 class Interpolator {
30 public:
31     Interpolator() {};
32     virtual ~Interpolator() {};
34 //    virtual Piecewise<D2<SBasis> > interpolateToPwD2Sb(std::vector<Point> points) = 0;
35     virtual Path interpolateToPath(std::vector<Point> points) = 0;
37 private:
38     Interpolator(const Interpolator&);
39     Interpolator& operator=(const Interpolator&);
40 };
42 class Linear : public Interpolator {
43 public:
44     Linear() {};
45     virtual ~Linear() {};
47     virtual Path interpolateToPath(std::vector<Point> points) {
48         Path path;
49         path.start( points.at(0) );
50         for (unsigned int i = 1 ; i < points.size(); ++i) {
51             path.appendNew<Geom::LineSegment>(points.at(i));
52         }
53         return path;
54     };
56 private:
57     Linear(const Linear&);
58     Linear& operator=(const Linear&);
59 };
61 // this class is terrible
62 class CubicBezierFit : public Interpolator {
63 public:
64     CubicBezierFit() {};
65     virtual ~CubicBezierFit() {};
67     virtual Path interpolateToPath(std::vector<Point> points) {
68         unsigned int n_points = points.size();
69         // worst case gives us 2 segment per point
70         int max_segs = 8*n_points;
71         Geom::Point * b = g_new(Geom::Point, max_segs);
72         Geom::Point * points_array = g_new(Geom::Point, 4*n_points);
73         for (unsigned i = 0; i < n_points; ++i) {
74             points_array[i] = points.at(i);
75         }
77         double tolerance_sq = 0; // this value is just a random guess
79         int const n_segs = Geom::bezier_fit_cubic_r(b, points_array, n_points,
80                                                  tolerance_sq, max_segs);
82         Geom::Path fit;
83         if ( n_segs > 0)
84         {
85             fit.start(b[0]);
86             for (int c = 0; c < n_segs; c++) {
87                 fit.appendNew<Geom::CubicBezier>(b[4*c+1], b[4*c+2], b[4*c+3]);
88             }
89         }
90         g_free(b);
91         g_free(points_array);
92         return fit;
93     };
95 private:
96     CubicBezierFit(const CubicBezierFit&);
97     CubicBezierFit& operator=(const CubicBezierFit&);
98 };
100 /// @todo invent name for this class
101 class CubicBezierJohan : public Interpolator {
102 public:
103     CubicBezierJohan() {};
104     virtual ~CubicBezierJohan() {};
106     virtual Path interpolateToPath(std::vector<Point> points) {
107         Path fit;
108         fit.start(points.at(0));
109         for (unsigned int i = 1; i < points.size(); ++i) {
110             Point p0 = points.at(i-1);
111             Point p1 = points.at(i);
112             Point dx = Point(p1[X] - p0[X], 0);
113             fit.appendNew<CubicBezier>(p0+0.2*dx, p1-0.2*dx, p1);
114         }
115         return fit;
116     };
118 private:
119     CubicBezierJohan(const CubicBezierJohan&);
120     CubicBezierJohan& operator=(const CubicBezierJohan&);
121 };
123 } //namespace Interpolate
124 } //namespace Geom
126 namespace Inkscape {
127 namespace LivePathEffect {
129 LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) :
130     Effect(lpeobject),
131     offset_points(_("Offset points"), _("Offset points"), "offset_points", &wr, this),
132     sort_points(_("Sort points"), _("Sort offset points according to their time value along the curve."), "sort_points", &wr, this, true)
134     show_orig_path = true;
136     registerParameter( dynamic_cast<Parameter *>(&offset_points) );
137     registerParameter( dynamic_cast<Parameter *>(&sort_points) );
140 LPEPowerStroke::~LPEPowerStroke()
146 void
147 LPEPowerStroke::doOnApply(SPLPEItem *lpeitem)
149     std::vector<Geom::Point> points;
150     points.push_back( *(SP_SHAPE(lpeitem)->curve->first_point()) );
151     Geom::Path const *path = SP_SHAPE(lpeitem)->curve->first_path();
152     points.push_back( path->pointAt(path->size()/2) );
153     points.push_back( *(SP_SHAPE(lpeitem)->curve->last_point()) );
154     offset_points.param_set_and_write_new_value(points);
157 static bool compare_offsets (Geom::Point first, Geom::Point second)
159     return first[Geom::X] < second[Geom::X];
163 Geom::Piecewise<Geom::D2<Geom::SBasis> >
164 LPEPowerStroke::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
166     using namespace Geom;
168     // perhaps use std::list instead of std::vector?
169     std::vector<Geom::Point> ts(offset_points.data().size() + 2);
170     // first and last point coincide with input path (for now at least)
171     ts.front() = Point(pwd2_in.domain().min(),0);
172     ts.back()  = Point(pwd2_in.domain().max(),0);
173     for (unsigned int i = 0; i < offset_points.data().size(); ++i) {
174         double t = nearest_point(offset_points.data().at(i), pwd2_in);
175         double offset = L2(pwd2_in.valueAt(t) - offset_points.data().at(i));
176         ts.at(i+1) = Geom::Point(t, offset);
177     }
179     if (sort_points) {
180         sort(ts.begin(), ts.end(), compare_offsets);
181     }
183     // create stroke path where points (x,y) = (t, offset)
184     Geom::Interpolate::CubicBezierJohan interpolator;
185     Path strokepath = interpolator.interpolateToPath(ts);
186     Path mirroredpath = strokepath.reverse() * Geom::Scale(1,-1);
187     strokepath.append(mirroredpath, Geom::Path::STITCH_DISCONTINUOUS);
188     strokepath.close();
190     D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
191     Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
192     Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
194     Piecewise<D2<SBasis> > der = unitVector(derivative(pwd2_in));
195     Piecewise<D2<SBasis> > n   = rot90(der);
197 //    output  = pwd2_in + n * offset;
198 //    append_half_circle(output, pwd2_in.lastValue(), n.lastValue() * offset);
199 //    output.continuousConcat(reverse(pwd2_in - n * offset));
200 //    append_half_circle(output, pwd2_in.firstValue(), -n.firstValue() * offset);
202     Piecewise<D2<SBasis> > output = compose(pwd2_in,x) + y*compose(n,x);
203     return output;
206 /* ######################## */
208 } //namespace LivePathEffect
209 } /* namespace Inkscape */
211 /*
212   Local Variables:
213   mode:c++
214   c-file-style:"stroustrup"
215   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
216   indent-tabs-mode:nil
217   fill-column:99
218   End:
219 */
220 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :