diff options
Diffstat (limited to 'src/2geom/solve-bezier-one-d.cpp')
-rw-r--r-- | src/2geom/solve-bezier-one-d.cpp | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/src/2geom/solve-bezier-one-d.cpp b/src/2geom/solve-bezier-one-d.cpp new file mode 100644 index 0000000..b82d20b --- /dev/null +++ b/src/2geom/solve-bezier-one-d.cpp @@ -0,0 +1,243 @@ + +#include <2geom/solver.h> +#include <2geom/choose.h> +#include <2geom/bezier.h> +#include <2geom/point.h> + +#include <cmath> +#include <algorithm> +//#include <valarray> + +/*** Find the zeros of the bernstein function. The code subdivides until it is happy with the + * linearity of the function. This requires an O(degree^2) subdivision for each step, even when + * there is only one solution. + */ + +namespace Geom { + +template<class t> +static int SGN(t x) { return (x > 0 ? 1 : (x < 0 ? -1 : 0)); } + +//const unsigned MAXDEPTH = 23; // Maximum depth for recursion. Using floats means 23 bits precision max + +//const double BEPSILON = ldexp(1.0,(-MAXDEPTH-1)); /*Flatness control value */ +//const double SECANT_EPSILON = 1e-13; // secant method converges much faster, get a bit more precision +/** + * This function is called _a lot_. We have included various manual memory management stuff to reduce the amount of mallocing that goes on. In the future it is possible that this will hurt performance. + **/ +class Bernsteins +{ +public: + static constexpr size_t MAX_DEPTH = 53; + size_t degree, N; + std::vector<double> &solutions; + + Bernsteins(size_t _degree, std::vector<double> &sol) + : degree(_degree), N(degree+1), solutions(sol) + { + } + + unsigned + control_poly_flat_enough(double const *V); + + void + find_bernstein_roots(double const *w, /* The control points */ + unsigned depth, /* The depth of the recursion */ + double left_t, double right_t); +}; +/* + * find_bernstein_roots : Given an equation in Bernstein-Bernstein form, find all + * of the roots in the open interval (0, 1). Return the number of roots found. + */ +void +find_bernstein_roots(double const *w, /* The control points */ + unsigned degree, /* The degree of the polynomial */ + std::vector<double> &solutions, /* RETURN candidate t-values */ + unsigned depth, /* The depth of the recursion */ + double left_t, double right_t, bool /*use_secant*/) +{ + Bernsteins B(degree, solutions); + B.find_bernstein_roots(w, depth, left_t, right_t); +} + +void +find_bernstein_roots(std::vector<double> &solutions, /* RETURN candidate t-values */ + Geom::Bezier const &bz, /* The control points */ + double left_t, double right_t) +{ + Bernsteins B(bz.degree(), solutions); + Geom::Bezier& bzl = const_cast<Geom::Bezier&>(bz); + double* w = &(bzl[0]); + B.find_bernstein_roots(w, 0, left_t, right_t); +} + + + +void Bernsteins::find_bernstein_roots(double const *w, /* The control points */ + unsigned depth, /* The depth of the recursion */ + double left_t, + double right_t) +{ + + size_t n_crossings = 0; + + int old_sign = SGN(w[0]); + //std::cout << "w[0] = " << w[0] << std::endl; + for (size_t i = 1; i < N; i++) + { + //std::cout << "w[" << i << "] = " << w[i] << std::endl; + int sign = SGN(w[i]); + if (sign != 0) + { + if (sign != old_sign && old_sign != 0) + { + ++n_crossings; + } + old_sign = sign; + } + } + //std::cout << "n_crossings = " << n_crossings << std::endl; + if (n_crossings == 0) return; // no solutions here + + if (n_crossings == 1) /* Unique solution */ + { + //std::cout << "depth = " << depth << std::endl; + /* Stop recursion when the tree is deep enough */ + /* if deep enough, return 1 solution at midpoint */ + if (depth > MAX_DEPTH) + { + //printf("bottom out %d\n", depth); + const double Ax = right_t - left_t; + const double Ay = w[degree] - w[0]; + + solutions.push_back(left_t - Ax*w[0] / Ay); + return; + } + + + double s = 0, t = 1; + double e = 1e-10; + int side = 0; + double r, fs = w[0], ft = w[degree]; + + for (size_t n = 0; n < 100; ++n) + { + r = (fs*t - ft*s) / (fs - ft); + if (fabs(t-s) < e * fabs(t+s)) break; + + double fr = bernstein_value_at(r, w, degree); + + if (fr * ft > 0) + { + t = r; ft = fr; + if (side == -1) fs /= 2; + side = -1; + } + else if (fs * fr > 0) + { + s = r; fs = fr; + if (side == +1) ft /= 2; + side = +1; + } + else break; + } + solutions.push_back(r*right_t + (1-r)*left_t); + return; + + } + + /* Otherwise, solve recursively after subdividing control polygon */ +// double Left[N], /* New left and right */ +// Right[N]; /* control polygons */ + //const double t = 0.5; + double* LR = new double[2*N]; + double* Left = LR; + double* Right = LR + N; + + std::copy(w, w + N, Right); + + Left[0] = Right[0]; + for (size_t i = 1; i < N; ++i) + { + for (size_t j = 0; j < N-i; ++j) + { + Right[j] = (Right[j] + Right[j+1]) * 0.5; + } + Left[i] = Right[0]; + } + + double mid_t = (left_t + right_t) * 0.5; + + + find_bernstein_roots(Left, depth+1, left_t, mid_t); + + + /* Solution is exactly on the subdivision point. */ + if (Right[0] == 0) + { + solutions.push_back(mid_t); + } + + find_bernstein_roots(Right, depth+1, mid_t, right_t); + delete[] LR; +} + +#if 0 +/* + * control_poly_flat_enough : + * Check if the control polygon of a Bernstein curve is flat enough + * for recursive subdivision to bottom out. + * + */ +unsigned +Bernsteins::control_poly_flat_enough(double const *V) +{ + /* Find the perpendicular distance from each interior control point to line connecting V[0] and + * V[degree] */ + + /* Derive the implicit equation for line connecting first */ + /* and last control points */ + const double a = V[0] - V[degree]; + + double max_distance_above = 0.0; + double max_distance_below = 0.0; + double ii = 0, dii = 1./degree; + for (unsigned i = 1; i < degree; i++) { + ii += dii; + /* Compute distance from each of the points to that line */ + const double d = (a + V[i]) * ii - a; + double dist = d*d; + // Find the largest distance + if (d < 0.0) + max_distance_below = std::min(max_distance_below, -dist); + else + max_distance_above = std::max(max_distance_above, dist); + } + + const double abSquared = 1./((a * a) + 1); + + const double intercept_1 = (a - max_distance_above * abSquared); + const double intercept_2 = (a - max_distance_below * abSquared); + + /* Compute bounding interval*/ + const double left_intercept = std::min(intercept_1, intercept_2); + const double right_intercept = std::max(intercept_1, intercept_2); + + const double error = 0.5 * (right_intercept - left_intercept); + //printf("error %g %g %g\n", error, a, BEPSILON * a); + return error < BEPSILON * a; +} +#endif + +} // namespace Geom + +/* + 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 : |