| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include <2geom/solver.h> | ||
| 2 | #include <2geom/choose.h> | ||
| 3 | #include <2geom/bezier.h> | ||
| 4 | #include <2geom/point.h> | ||
| 5 | |||
| 6 | #include <cmath> | ||
| 7 | #include <algorithm> | ||
| 8 | |||
| 9 | /*** Find the zeros of a Bezier. The code subdivides until it is happy with the linearity of the | ||
| 10 | * function. This requires an O(degree^2) subdivision for each step, even when there is only one | ||
| 11 | * solution. | ||
| 12 | * | ||
| 13 | * We try fairly hard to correctly handle multiple roots. | ||
| 14 | */ | ||
| 15 | |||
| 16 | //#define debug(x) do{x;}while(0) | ||
| 17 | #define debug(x) | ||
| 18 | |||
| 19 | namespace Geom { | ||
| 20 | namespace { | ||
| 21 | |||
| 22 | struct Bernsteins | ||
| 23 | { | ||
| 24 | static constexpr size_t MAX_DEPTH = 22; | ||
| 25 | std::vector<double> &solutions; | ||
| 26 | |||
| 27 | 65241 | Bernsteins(std::vector<double> &sol) | |
| 28 | 65241 | : solutions{sol} | |
| 29 | 65241 | {} | |
| 30 | |||
| 31 | double secant(Bezier const &bz); | ||
| 32 | |||
| 33 | void find_bernstein_roots(Bezier const &bz, unsigned depth, | ||
| 34 | double left_t, double right_t); | ||
| 35 | }; | ||
| 36 | |||
| 37 | } // namespace | ||
| 38 | |||
| 39 | void | ||
| 40 | 67906 | Bezier::find_bezier_roots(std::vector<double> &solutions, | |
| 41 | double left_t, double right_t) const { | ||
| 42 |
1/2✓ Branch 2 taken 67906 times.
✗ Branch 3 not taken.
|
67906 | Bezier bz = *this; |
| 43 | |||
| 44 | // a constant bezier, even if identically zero, has no roots | ||
| 45 |
3/4✓ Branch 1 taken 67906 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 241 times.
✓ Branch 4 taken 67665 times.
|
67906 | if (bz.isConstant()) { |
| 46 | 241 | return; | |
| 47 | } | ||
| 48 | |||
| 49 |
2/2✓ Branch 1 taken 5170 times.
✓ Branch 2 taken 67665 times.
|
72835 | while(bz[0] == 0) { |
| 50 | debug(std::cout << "deflate\n"); | ||
| 51 |
2/4✓ Branch 2 taken 5170 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 5170 times.
✗ Branch 6 not taken.
|
5170 | bz = bz.deflate(); |
| 52 |
1/2✓ Branch 2 taken 5170 times.
✗ Branch 3 not taken.
|
5170 | solutions.push_back(0); |
| 53 | } | ||
| 54 |
3/4✓ Branch 1 taken 67665 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2424 times.
✓ Branch 4 taken 65241 times.
|
67665 | if (bz.degree() == 1) { |
| 55 | debug(std::cout << "linear\n"); | ||
| 56 | |||
| 57 |
2/2✓ Branch 4 taken 2348 times.
✓ Branch 5 taken 76 times.
|
2424 | if (Geom::sgn(bz[0]) != Geom::sgn(bz[1])) { |
| 58 | 2348 | double d = bz[0] - bz[1]; | |
| 59 |
1/2✓ Branch 0 taken 2348 times.
✗ Branch 1 not taken.
|
2348 | if(d != 0) { |
| 60 | 2348 | double r = bz[0] / d; | |
| 61 |
2/4✓ Branch 0 taken 2348 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2348 times.
✗ Branch 3 not taken.
|
2348 | if(0 <= r && r <= 1) |
| 62 |
1/2✓ Branch 1 taken 2348 times.
✗ Branch 2 not taken.
|
2348 | solutions.push_back(r); |
| 63 | } | ||
| 64 | } | ||
| 65 | 2424 | return; | |
| 66 | } | ||
| 67 | |||
| 68 | //std::cout << "initial = " << bz << std::endl; | ||
| 69 | 65241 | Bernsteins B(solutions); | |
| 70 |
1/2✓ Branch 1 taken 65241 times.
✗ Branch 2 not taken.
|
65241 | B.find_bernstein_roots(bz, 0, left_t, right_t); |
| 71 | //std::cout << solutions << std::endl; | ||
| 72 |
2/2✓ Branch 1 taken 65241 times.
✓ Branch 2 taken 2665 times.
|
67906 | } |
| 73 | |||
| 74 | 172835 | void Bernsteins::find_bernstein_roots(Bezier const &bz, | |
| 75 | unsigned depth, | ||
| 76 | double left_t, | ||
| 77 | double right_t) | ||
| 78 | { | ||
| 79 | debug(std::cout << left_t << ", " << right_t << std::endl); | ||
| 80 | 172835 | size_t n_crossings = 0; | |
| 81 | |||
| 82 | 172835 | int old_sign = Geom::sgn(bz[0]); | |
| 83 | //std::cout << "w[0] = " << bz[0] << std::endl; | ||
| 84 |
3/4✓ Branch 1 taken 1013508 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 840673 times.
✓ Branch 4 taken 172835 times.
|
1013508 | for (size_t i = 1; i < bz.size(); i++) |
| 85 | { | ||
| 86 | //std::cout << "w[" << i << "] = " << w[i] << std::endl; | ||
| 87 | 840673 | int sign = Geom::sgn(bz[i]); | |
| 88 |
2/2✓ Branch 0 taken 833317 times.
✓ Branch 1 taken 7356 times.
|
840673 | if (sign != 0) |
| 89 | { | ||
| 90 |
3/4✓ Branch 0 taken 233263 times.
✓ Branch 1 taken 600054 times.
✓ Branch 2 taken 233263 times.
✗ Branch 3 not taken.
|
833317 | if (sign != old_sign && old_sign != 0) |
| 91 | { | ||
| 92 | 233263 | ++n_crossings; | |
| 93 | } | ||
| 94 | 833317 | old_sign = sign; | |
| 95 | } | ||
| 96 | } | ||
| 97 | // if last control point is zero, that counts as crossing too | ||
| 98 |
3/4✓ Branch 1 taken 172835 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7349 times.
✓ Branch 5 taken 165486 times.
|
172835 | if (bz[bz.size() - 1] == 0) { |
| 99 | 7349 | ++n_crossings; | |
| 100 | } | ||
| 101 | |||
| 102 | //std::cout << "n_crossings = " << n_crossings << std::endl; | ||
| 103 |
2/2✓ Branch 0 taken 66205 times.
✓ Branch 1 taken 106630 times.
|
172835 | if (n_crossings == 0) return; // no solutions here |
| 104 | |||
| 105 |
2/2✓ Branch 0 taken 52655 times.
✓ Branch 1 taken 53975 times.
|
106630 | if (n_crossings == 1) /* Unique solution */ |
| 106 | { | ||
| 107 | //std::cout << "depth = " << depth << std::endl; | ||
| 108 | /* Stop recursion when the tree is deep enough */ | ||
| 109 | /* if deep enough, return 1 solution at midpoint */ | ||
| 110 |
2/2✓ Branch 0 taken 72 times.
✓ Branch 1 taken 52583 times.
|
52655 | if (depth > MAX_DEPTH) |
| 111 | { | ||
| 112 | //printf("bottom out %d\n", depth); | ||
| 113 | 72 | const double Ax = right_t - left_t; | |
| 114 |
1/2✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
|
72 | const double Ay = bz.at1() - bz.at0(); |
| 115 | |||
| 116 |
1/2✓ Branch 3 taken 72 times.
✗ Branch 4 not taken.
|
72 | solutions.push_back(left_t - Ax*bz.at0() / Ay); |
| 117 | 72 | return; | |
| 118 | } | ||
| 119 | |||
| 120 |
1/2✓ Branch 1 taken 52583 times.
✗ Branch 2 not taken.
|
52583 | double r = secant(bz); |
| 121 |
1/2✓ Branch 2 taken 52583 times.
✗ Branch 3 not taken.
|
52583 | solutions.push_back(r*right_t + (1-r)*left_t); |
| 122 | 52583 | return; | |
| 123 | } | ||
| 124 | /* Otherwise, solve recursively after subdividing control polygon */ | ||
| 125 |
1/2✓ Branch 2 taken 53975 times.
✗ Branch 3 not taken.
|
53975 | Bezier::Order o(bz); |
| 126 |
2/4✓ Branch 2 taken 53975 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 53975 times.
✗ Branch 7 not taken.
|
53975 | Bezier Left(o), Right = bz; |
| 127 | 53975 | double split_t = (left_t + right_t) * 0.5; | |
| 128 | |||
| 129 | // If subdivision is working poorly, split around the leftmost root of the derivative | ||
| 130 |
2/2✓ Branch 0 taken 22188 times.
✓ Branch 1 taken 31787 times.
|
53975 | if (depth > 2) { |
| 131 | debug(std::cout << "derivative mode\n"); | ||
| 132 |
1/2✓ Branch 2 taken 22188 times.
✗ Branch 3 not taken.
|
22188 | Bezier dbz = derivative(bz); |
| 133 | |||
| 134 | debug(std::cout << "initial = " << dbz << std::endl); | ||
| 135 |
2/4✓ Branch 3 taken 22188 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 22188 times.
✗ Branch 7 not taken.
|
22188 | std::vector<double> dsolutions = dbz.roots(Interval(left_t, right_t)); |
| 136 | debug(std::cout << "dsolutions = " << dsolutions << std::endl); | ||
| 137 | |||
| 138 | 22188 | double dsplit_t = 0.5; | |
| 139 |
2/2✓ Branch 1 taken 18291 times.
✓ Branch 2 taken 3897 times.
|
22188 | if(!dsolutions.empty()) { |
| 140 | 18291 | dsplit_t = dsolutions[0]; | |
| 141 | 18291 | split_t = left_t + (right_t - left_t)*dsplit_t; | |
| 142 | debug(std::cout << "split_value = " << bz(split_t) << std::endl); | ||
| 143 | debug(std::cout << "splitting around " << dsplit_t << " = " | ||
| 144 | << split_t << "\n"); | ||
| 145 | |||
| 146 | } | ||
| 147 |
1/2✓ Branch 2 taken 22188 times.
✗ Branch 3 not taken.
|
22188 | std::pair<Bezier, Bezier> LR = bz.subdivide(dsplit_t); |
| 148 |
1/2✓ Branch 1 taken 22188 times.
✗ Branch 2 not taken.
|
22188 | Left = LR.first; |
| 149 |
1/2✓ Branch 1 taken 22188 times.
✗ Branch 2 not taken.
|
22188 | Right = LR.second; |
| 150 | 22188 | } else { | |
| 151 | // split at midpoint, because it is cheap | ||
| 152 | 31787 | Left[0] = Right[0]; | |
| 153 |
3/4✓ Branch 1 taken 193243 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 161456 times.
✓ Branch 4 taken 31787 times.
|
193243 | for (size_t i = 1; i < bz.size(); ++i) |
| 154 | { | ||
| 155 |
3/4✓ Branch 1 taken 911933 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 750477 times.
✓ Branch 4 taken 161456 times.
|
911933 | for (size_t j = 0; j < bz.size()-i; ++j) |
| 156 | { | ||
| 157 | 750477 | Right[j] = (Right[j] + Right[j+1]) * 0.5; | |
| 158 | } | ||
| 159 | 161456 | Left[i] = Right[0]; | |
| 160 | } | ||
| 161 | } | ||
| 162 | debug(std::cout << "Solution is exactly on the subdivision point.\n"); | ||
| 163 | debug(std::cout << Left << " , " << Right << std::endl); | ||
| 164 |
2/4✓ Branch 2 taken 53975 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 53975 times.
✗ Branch 6 not taken.
|
53975 | Left = reverse(Left); |
| 165 |
7/8✓ Branch 1 taken 71556 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 71378 times.
✓ Branch 4 taken 178 times.
✓ Branch 6 taken 17581 times.
✓ Branch 7 taken 53797 times.
✓ Branch 8 taken 17581 times.
✓ Branch 9 taken 53975 times.
|
71556 | while(Right.order() > 0 && fabs(Right[0]) <= 1e-10) { |
| 166 | debug(std::cout << "deflate\n"); | ||
| 167 |
2/4✓ Branch 2 taken 17581 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 17581 times.
✗ Branch 6 not taken.
|
17581 | Right = Right.deflate(); |
| 168 |
2/4✓ Branch 2 taken 17581 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 17581 times.
✗ Branch 6 not taken.
|
17581 | Left = Left.deflate(); |
| 169 |
1/2✓ Branch 1 taken 17581 times.
✗ Branch 2 not taken.
|
17581 | solutions.push_back(split_t); |
| 170 | } | ||
| 171 |
2/4✓ Branch 2 taken 53975 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 53975 times.
✗ Branch 6 not taken.
|
53975 | Left = reverse(Left); |
| 172 |
3/4✓ Branch 1 taken 53975 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 53797 times.
✓ Branch 4 taken 178 times.
|
53975 | if (Right.order() > 0) { |
| 173 | debug(std::cout << Left << " , " << Right << std::endl); | ||
| 174 |
1/2✓ Branch 1 taken 53797 times.
✗ Branch 2 not taken.
|
53797 | find_bernstein_roots(Left, depth+1, left_t, split_t); |
| 175 |
1/2✓ Branch 1 taken 53797 times.
✗ Branch 2 not taken.
|
53797 | find_bernstein_roots(Right, depth+1, split_t, right_t); |
| 176 | } | ||
| 177 | 53975 | } | |
| 178 | |||
| 179 | 52583 | double Bernsteins::secant(Bezier const &bz) { | |
| 180 | 52583 | double s = 0, t = 1; | |
| 181 | 52583 | double e = 1e-14; | |
| 182 | 52583 | int side = 0; | |
| 183 | 52583 | double r, fs = bz.at0(), ft = bz.at1(); | |
| 184 | |||
| 185 |
1/2✓ Branch 0 taken 577795 times.
✗ Branch 1 not taken.
|
577795 | for (size_t n = 0; n < 100; ++n) |
| 186 | { | ||
| 187 | 577795 | r = (fs*t - ft*s) / (fs - ft); | |
| 188 |
2/2✓ Branch 0 taken 41986 times.
✓ Branch 1 taken 535809 times.
|
577795 | if (fabs(t-s) < e * fabs(t+s)) { |
| 189 | debug(std::cout << "error small " << fabs(t-s) | ||
| 190 | << ", accepting solution " << r | ||
| 191 | << "after " << n << "iterations\n"); | ||
| 192 | 41986 | return r; | |
| 193 | } | ||
| 194 | |||
| 195 | 535809 | double fr = bz.valueAt(r); | |
| 196 | |||
| 197 |
2/2✓ Branch 0 taken 316210 times.
✓ Branch 1 taken 219599 times.
|
535809 | if (fr * ft > 0) |
| 198 | { | ||
| 199 | 316210 | t = r; ft = fr; | |
| 200 |
2/2✓ Branch 0 taken 148322 times.
✓ Branch 1 taken 167888 times.
|
316210 | if (side == -1) fs /= 2; |
| 201 | 316210 | side = -1; | |
| 202 | } | ||
| 203 |
2/2✓ Branch 0 taken 209002 times.
✓ Branch 1 taken 10597 times.
|
219599 | else if (fs * fr > 0) |
| 204 | { | ||
| 205 | 209002 | s = r; fs = fr; | |
| 206 |
2/2✓ Branch 0 taken 45498 times.
✓ Branch 1 taken 163504 times.
|
209002 | if (side == +1) ft /= 2; |
| 207 | 209002 | side = +1; | |
| 208 | } | ||
| 209 | 10597 | else break; | |
| 210 | } | ||
| 211 | 10597 | return r; | |
| 212 | } | ||
| 213 | |||
| 214 | } // namespace Geom | ||
| 215 | |||
| 216 | /* | ||
| 217 | Local Variables: | ||
| 218 | mode:c++ | ||
| 219 | c-file-style:"stroustrup" | ||
| 220 | c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) | ||
| 221 | indent-tabs-mode:nil | ||
| 222 | fill-column:99 | ||
| 223 | End: | ||
| 224 | */ | ||
| 225 | // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : | ||
| 226 |