diff options
Diffstat (limited to 'src/live_effects/lpe-powerstroke-interpolators.h')
-rw-r--r-- | src/live_effects/lpe-powerstroke-interpolators.h | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/live_effects/lpe-powerstroke-interpolators.h b/src/live_effects/lpe-powerstroke-interpolators.h new file mode 100644 index 0000000..940d97e --- /dev/null +++ b/src/live_effects/lpe-powerstroke-interpolators.h @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Interpolators for lists of points. + */ +/* Authors: + * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl> + * + * Copyright (C) 2010-2011 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_LPE_POWERSTROKE_INTERPOLATORS_H +#define INKSCAPE_LPE_POWERSTROKE_INTERPOLATORS_H + +#include <2geom/path.h> +#include <2geom/bezier-utils.h> +#include <2geom/sbasis-to-bezier.h> + +#include "live_effects/spiro.h" + + +/// @TODO Move this to 2geom? +namespace Geom { +namespace Interpolate { + +enum InterpolatorType { + INTERP_LINEAR, + INTERP_CUBICBEZIER, + INTERP_CUBICBEZIER_JOHAN, + INTERP_SPIRO, + INTERP_CUBICBEZIER_SMOOTH, + INTERP_CENTRIPETAL_CATMULLROM +}; + +class Interpolator { +public: + Interpolator() = default;; + virtual ~Interpolator() = default;; + + static Interpolator* create(InterpolatorType type); + + virtual Geom::Path interpolateToPath(std::vector<Point> const &points) const = 0; + +private: + Interpolator(const Interpolator&) = delete; + Interpolator& operator=(const Interpolator&) = delete; +}; + +class Linear : public Interpolator { +public: + Linear() = default;; + ~Linear() override = default;; + + Path interpolateToPath(std::vector<Point> const &points) const override { + 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&) = delete; + Linear& operator=(const Linear&) = delete; +}; + +// this class is terrible +class CubicBezierFit : public Interpolator { +public: + CubicBezierFit() = default;; + ~CubicBezierFit() override = default;; + + Path interpolateToPath(std::vector<Point> const &points) const override { + 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&) = delete; + CubicBezierFit& operator=(const CubicBezierFit&) = delete; +}; + +/// @todo invent name for this class +class CubicBezierJohan : public Interpolator { +public: + CubicBezierJohan(double beta = 0.2) { + _beta = beta; + }; + ~CubicBezierJohan() override = default;; + + Path interpolateToPath(std::vector<Point> const &points) const override { + 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+_beta*dx, p1-_beta*dx, p1); + } + return fit; + }; + + void setBeta(double beta) { + _beta = beta; + } + + double _beta; + +private: + CubicBezierJohan(const CubicBezierJohan&) = delete; + CubicBezierJohan& operator=(const CubicBezierJohan&) = delete; +}; + +/// @todo invent name for this class +class CubicBezierSmooth : public Interpolator { +public: + CubicBezierSmooth(double beta = 0.2) { + _beta = beta; + }; + ~CubicBezierSmooth() override = default;; + + Path interpolateToPath(std::vector<Point> const &points) const override { + Path fit; + fit.start(points.at(0)); + unsigned int num_points = points.size(); + for (unsigned int i = 1; i < num_points; ++i) { + Point p0 = points.at(i-1); + Point p1 = points.at(i); + Point dx = Point(p1[X] - p0[X], 0); + if (i == 1) { + fit.appendNew<CubicBezier>(p0, p1-0.75*dx, p1); + } else if (i == points.size() - 1) { + fit.appendNew<CubicBezier>(p0+0.75*dx, p1, p1); + } else { + fit.appendNew<CubicBezier>(p0+_beta*dx, p1-_beta*dx, p1); + } + } + return fit; + }; + + void setBeta(double beta) { + _beta = beta; + } + + double _beta; + +private: + CubicBezierSmooth(const CubicBezierSmooth&) = delete; + CubicBezierSmooth& operator=(const CubicBezierSmooth&) = delete; +}; + +class SpiroInterpolator : public Interpolator { +public: + SpiroInterpolator() = default;; + ~SpiroInterpolator() override = default;; + + Path interpolateToPath(std::vector<Point> const &points) const override { + Path fit; + + Coord scale_y = 100.; + + guint len = points.size(); + Spiro::spiro_cp *controlpoints = g_new (Spiro::spiro_cp, len); + for (unsigned int i = 0; i < len; ++i) { + controlpoints[i].x = points[i][X]; + controlpoints[i].y = points[i][Y] / scale_y; + controlpoints[i].ty = 'c'; + } + controlpoints[0].ty = '{'; + controlpoints[1].ty = 'v'; + controlpoints[len-2].ty = 'v'; + controlpoints[len-1].ty = '}'; + + Spiro::spiro_run(controlpoints, len, fit); + + fit *= Scale(1,scale_y); + g_free(controlpoints); + return fit; + }; + +private: + SpiroInterpolator(const SpiroInterpolator&) = delete; + SpiroInterpolator& operator=(const SpiroInterpolator&) = delete; +}; + +// Quick mockup for testing the behavior for powerstroke controlpoint interpolation +class CentripetalCatmullRomInterpolator : public Interpolator { +public: + CentripetalCatmullRomInterpolator() = default;; + ~CentripetalCatmullRomInterpolator() override = default;; + + Path interpolateToPath(std::vector<Point> const &points) const override { + unsigned int n_points = points.size(); + + Geom::Path fit(points.front()); + + if (n_points < 3) return fit; // TODO special cases for 0,1 and 2 input points + + // return n_points-1 cubic segments + + // duplicate first point + fit.append(calc_bezier(points[0],points[0],points[1],points[2])); + + for (std::size_t i = 0; i < n_points-2; ++i) { + Point p0 = points[i]; + Point p1 = points[i+1]; + Point p2 = points[i+2]; + Point p3 = (i < n_points-3) ? points[i+3] : points[i+2]; + + fit.append(calc_bezier(p0, p1, p2, p3)); + } + + return fit; + }; + +private: + CubicBezier calc_bezier(Point p0, Point p1, Point p2, Point p3) const { + // create interpolating bezier between p1 and p2 + + // Part of the code comes from StackOverflow user eriatarka84 + // http://stackoverflow.com/a/23980479/2929337 + + // calculate time coords (deltas) of points + // the factor 0.25 can be generalized for other Catmull-Rom interpolation types + // see alpha in Yuksel et al. "On the Parameterization of Catmull-Rom Curves", + // --> http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + double dt0 = powf(distanceSq(p0, p1), 0.25); + double dt1 = powf(distanceSq(p1, p2), 0.25); + double dt2 = powf(distanceSq(p2, p3), 0.25); + + + // safety check for repeated points + double eps = Geom::EPSILON; + if (dt1 < eps) + dt1 = 1.0; + if (dt0 < eps) + dt0 = dt1; + if (dt2 < eps) + dt2 = dt1; + + // compute tangents when parameterized in [t1,t2] + Point tan1 = (p1 - p0) / dt0 - (p2 - p0) / (dt0 + dt1) + (p2 - p1) / dt1; + Point tan2 = (p2 - p1) / dt1 - (p3 - p1) / (dt1 + dt2) + (p3 - p2) / dt2; + // rescale tangents for parametrization in [0,1] + tan1 *= dt1; + tan2 *= dt1; + + // create bezier from tangents (this is already in 2geom somewhere, or should be moved to it) + // the tangent of a bezier curve is: B'(t) = 3(1-t)^2 (b1 - b0) + 6(1-t)t(b2-b1) + 3t^2(b3-b2) + // So we have to make sure that B'(0) = tan1 and B'(1) = tan2, and we already know that b0=p1 and b3=p2 + // tan1 = B'(0) = 3 (b1 - p1) --> p1 + (tan1)/3 = b1 + // tan2 = B'(1) = 3 (p2 - b2) --> p2 - (tan2)/3 = b2 + + Point b0 = p1; + Point b1 = p1 + tan1 / 3; + Point b2 = p2 - tan2 / 3; + Point b3 = p2; + + return CubicBezier(b0, b1, b2, b3); + } + + CentripetalCatmullRomInterpolator(const CentripetalCatmullRomInterpolator&) = delete; + CentripetalCatmullRomInterpolator& operator=(const CentripetalCatmullRomInterpolator&) = delete; +}; + + +inline Interpolator* +Interpolator::create(InterpolatorType type) { + switch (type) { + case INTERP_LINEAR: + return new Geom::Interpolate::Linear(); + case INTERP_CUBICBEZIER: + return new Geom::Interpolate::CubicBezierFit(); + case INTERP_CUBICBEZIER_JOHAN: + return new Geom::Interpolate::CubicBezierJohan(); + case INTERP_SPIRO: + return new Geom::Interpolate::SpiroInterpolator(); + case INTERP_CUBICBEZIER_SMOOTH: + return new Geom::Interpolate::CubicBezierSmooth(); + case INTERP_CENTRIPETAL_CATMULLROM: + return new Geom::Interpolate::CentripetalCatmullRomInterpolator(); + default: + return new Geom::Interpolate::Linear(); + } +} + +} //namespace Interpolate +} //namespace Geom + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |