solve-bezier.cpp revision 6553067c3efe2755cef7c7dde1e3623ebfcabbab
5403N/A
2035N/A#include <2geom/solver.h>
2035N/A#include <2geom/choose.h>
2035N/A#include <2geom/bezier.h>
2035N/A#include <2geom/point.h>
2035N/A
2035N/A#include <cmath>
2035N/A#include <algorithm>
2035N/A
2035N/A/*** Find the zeros of a Bezier. The code subdivides until it is happy with the linearity of the
2035N/A * function. This requires an O(degree^2) subdivision for each step, even when there is only one
2035N/A * solution.
2035N/A *
2035N/A * We try fairly hard to correctly handle multiple roots.
2035N/A */
2035N/A
2035N/A//#define debug(x) do{x;}while(0)
2035N/A#define debug(x)
2035N/A
2035N/Anamespace Geom{
2035N/A
2035N/Atemplate<class t>
2035N/Astatic int SGN(t x) { return (x > 0 ? 1 : (x < 0 ? -1 : 0)); }
5774N/A
2035N/Aclass Bernsteins{
2035N/Apublic:
2035N/A static const size_t MAX_DEPTH = 22;
2035N/A std::vector<double> &solutions;
2035N/A //std::vector<double> dsolutions;
2035N/A
2035N/A Bernsteins(std::vector<double> & sol)
2035N/A : solutions(sol)
2035N/A {}
2035N/A
2035N/A void subdivide(double const *V,
2035N/A double t,
2035N/A double *Left,
2035N/A double *Right);
2035N/A
2035N/A double secant(Bezier bz);
2035N/A
2035N/A double horner(Bezier bz, double t);
2035N/A
2035N/A
2035N/A void find_bernstein_roots(Bezier bz, unsigned depth,
2035N/A double left_t, double right_t);
2035N/A};
5774N/A
5774N/Atemplate <typename T>
2035N/Ainline std::ostream &operator<< (std::ostream &out_file, const std::vector<T> & b) {
2035N/A out_file << "[";
2035N/A for(unsigned i = 0; i < b.size(); i++) {
2035N/A out_file << b[i] << ", ";
2035N/A }
2035N/A return out_file << "]";
2035N/A}
2035N/A
2035N/ABezier subRight(Bezier bz, double t) {
2035N/A unsigned order = bz.order();
2035N/A unsigned N = order+1;
2035N/A std::valarray<Coord> row(N);
2035N/A for (unsigned i = 0; i < N; i++)
2035N/A row[i] = bz[i];
2035N/A
2035N/A // Triangle computation
2035N/A const double omt = (1-t);
2035N/A Bezier Right = bz;
2035N/A Right[order] = row[order];
2035N/A for (unsigned i = 1; i < N; i++) {
2035N/A for (unsigned j = 0; j < N - i; j++) {
2035N/A row[j] = omt*row[j] + t*row[j+1];
2035N/A }
2035N/A Right[order-i] = row[order-i];
2035N/A }
2035N/A return Right;
5403N/A}
5403N/A
5403N/Avoid convex_hull_marching(Bezier src_bz, Bezier bz,
5403N/A std::vector<double> &solutions,
5403N/A double left_t,
5403N/A double right_t)
5403N/A{
5403N/A while(bz.order() > 0 and bz[0] == 0) {
5403N/A std::cout << "deflate\n";
5403N/A bz = bz.deflate();
2035N/A solutions.push_back(left_t);
2035N/A }
2035N/A if (bz.order() > 0) {
2035N/A
2035N/A int old_sign = SGN(bz[0]);
2035N/A
2035N/A int sign;
2035N/A double left_bound = 0;
2035N/A double dt = 0;
2035N/A for (size_t i = 1; i < bz.size(); i++)
2035N/A {
2035N/A sign = SGN(bz[i]);
2035N/A if (sign != old_sign)
2035N/A {
2035N/A dt = double(i) / bz.order();
2035N/A left_bound = dt * bz[0] / (bz[0] - bz[i]);
2035N/A break;
2035N/A }
2035N/A old_sign = sign;
}
if (dt == 0) return;
std::cout << bz << std::endl;
std::cout << "dt = " << dt << std::endl;
std::cout << "left_t = " << left_t << std::endl;
std::cout << "right_t = " << right_t << std::endl;
std::cout << "left bound = " << left_bound
<< " = " << bz(left_bound) << std::endl;
double new_left_t = left_bound * (right_t - left_t) + left_t;
std::cout << "new_left_t = " << new_left_t << std::endl;
Bezier bzr = subRight(src_bz, new_left_t);
while(bzr.order() > 0 and bzr[0] == 0) {
std::cout << "deflate\n";
bzr = bzr.deflate();
solutions.push_back(new_left_t);
}
if (left_t < new_left_t) {
convex_hull_marching(src_bz, bzr,
solutions,
new_left_t, right_t);
} else {
std::cout << "epsilon reached\n";
while(bzr.order() > 0 and fabs(bzr[0]) <= 1e-10) {
std::cout << "deflate\n";
bzr = bzr.deflate();
std::cout << bzr << std::endl;
solutions.push_back(new_left_t);
}
}
}
}
void
Bezier::find_bezier_roots(std::vector<double> &solutions,
double left_t, double right_t) const {
Bezier bz = *this;
//convex_hull_marching(bz, bz, solutions, left_t, right_t);
//return;
//A constant bezier, even if identically zero, has no roots
if (bz.isConstant())
return;
while(bz[0] == 0) {
debug(std::cout << "deflate\n");
bz = bz.deflate();
solutions.push_back(0);
}
if (bz.degree() == 1) {
debug(std::cout << "linear\n");
if (SGN(bz[0]) != SGN(bz[1])) {
double d = bz[0] - bz[1];
if(d != 0) {
double r = bz[0] / d;
if(0 <= r && r <= 1)
solutions.push_back(r);
}
}
return;
}
//std::cout << "initial = " << bz << std::endl;
Bernsteins B(solutions);
B.find_bernstein_roots(bz, 0, left_t, right_t);
//std::cout << solutions << std::endl;
}
void Bernsteins::find_bernstein_roots(Bezier bz,
unsigned depth,
double left_t,
double right_t)
{
debug(std::cout << left_t << ", " << right_t << std::endl);
size_t n_crossings = 0;
int old_sign = SGN(bz[0]);
//std::cout << "w[0] = " << bz[0] << std::endl;
int sign;
for (size_t i = 1; i < bz.size(); i++)
{
//std::cout << "w[" << i << "] = " << w[i] << std::endl;
sign = SGN(bz[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 = bz.at1() - bz.at0();
solutions.push_back(left_t - Ax*bz.at0() / Ay);
return;
}
double r = secant(bz);
solutions.push_back(r*right_t + (1-r)*left_t);
return;
}
/* Otherwise, solve recursively after subdividing control polygon */
Bezier::Order o(bz);
Bezier Left(o), Right = bz;
double split_t = (left_t + right_t) * 0.5;
// If subdivision is working poorly, split around the leftmost root of the derivative
if (depth > 2) {
debug(std::cout << "derivative mode\n");
Bezier dbz = derivative(bz);
debug(std::cout << "initial = " << dbz << std::endl);
std::vector<double> dsolutions = dbz.roots(Interval(left_t, right_t));
debug(std::cout << "dsolutions = " << dsolutions << std::endl);
double dsplit_t = 0.5;
if(dsolutions.size() > 0) {
dsplit_t = dsolutions[0];
split_t = left_t + (right_t - left_t)*dsplit_t;
debug(std::cout << "split_value = " << bz(split_t) << std::endl);
debug(std::cout << "spliting around " << dsplit_t << " = "
<< split_t << "\n");
}
std::pair<Bezier, Bezier> LR = bz.subdivide(dsplit_t);
Left = LR.first;
Right = LR.second;
} else {
// split at midpoint, because it is cheap
Left[0] = Right[0];
for (size_t i = 1; i < bz.size(); ++i)
{
for (size_t j = 0; j < bz.size()-i; ++j)
{
Right[j] = (Right[j] + Right[j+1]) * 0.5;
}
Left[i] = Right[0];
}
}
debug(std::cout << "Solution is exactly on the subdivision point.\n");
debug(std::cout << Left << " , " << Right << std::endl);
Left = reverse(Left);
while(Right.order() > 0 and fabs(Right[0]) <= 1e-10) {
debug(std::cout << "deflate\n");
Right = Right.deflate();
Left = Left.deflate();
solutions.push_back(split_t);
}
Left = reverse(Left);
if (Right.order() > 0) {
debug(std::cout << Left << " , " << Right << std::endl);
find_bernstein_roots(Left, depth+1, left_t, split_t);
find_bernstein_roots(Right, depth+1, split_t, right_t);
}
}
// suggested by Sederberg.
double Bernsteins::horner(Bezier bz, double t)
{
double u, tn, tmp;
u = 1.0 - t;
tn = 1.0;
tmp = bz.at0() * u;
for(size_t i = 1; i < bz.degree(); ++i)
{
tn *= t;
tmp = (tmp + tn*choose<double>(bz.order(), (unsigned)i)*bz[i]) * u;
}
return (tmp + tn*t*bz.at1());
}
double Bernsteins::secant(Bezier bz) {
double s = 0, t = 1;
double e = 1e-14;
int side = 0;
double r, fr, fs = bz.at0(), ft = bz.at1();
for (size_t n = 0; n < 100; ++n)
{
r = (fs*t - ft*s) / (fs - ft);
if (fabs(t-s) < e * fabs(t+s)) {
debug(std::cout << "error small " << fabs(t-s)
<< ", accepting solution " << r
<< "after " << n << "iterations\n");
return r;
}
fr = horner(bz, r);
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;
}
return r;
}
};
/*
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 :