caryclark@google.com | 9e49fb6 | 2012-08-27 14:11:33 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2012 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
caryclark@google.com | beda389 | 2013-02-07 13:13:41 +0000 | [diff] [blame] | 7 | #include "CubicUtilities.h" |
caryclark@google.com | 45a8fc6 | 2013-02-14 15:29:11 +0000 | [diff] [blame] | 8 | #include "Extrema.h" |
caryclark@google.com | 27accef | 2012-01-25 18:57:23 +0000 | [diff] [blame] | 9 | #include "QuadraticUtilities.h" |
caryclark@google.com | beda389 | 2013-02-07 13:13:41 +0000 | [diff] [blame] | 10 | #include "TriangleUtilities.h" |
| 11 | |
| 12 | // from http://blog.gludion.com/2009/08/distance-to-quadratic-bezier-curve.html |
| 13 | double nearestT(const Quadratic& quad, const _Point& pt) { |
caryclark@google.com | 7ff5c84 | 2013-02-26 15:56:05 +0000 | [diff] [blame^] | 14 | _Vector pos = quad[0] - pt; |
caryclark@google.com | beda389 | 2013-02-07 13:13:41 +0000 | [diff] [blame] | 15 | // search points P of bezier curve with PM.(dP / dt) = 0 |
| 16 | // a calculus leads to a 3d degree equation : |
caryclark@google.com | 7ff5c84 | 2013-02-26 15:56:05 +0000 | [diff] [blame^] | 17 | _Vector A = quad[1] - quad[0]; |
| 18 | _Vector B = quad[2] - quad[1]; |
caryclark@google.com | beda389 | 2013-02-07 13:13:41 +0000 | [diff] [blame] | 19 | B -= A; |
| 20 | double a = B.dot(B); |
| 21 | double b = 3 * A.dot(B); |
| 22 | double c = 2 * A.dot(A) + pos.dot(B); |
| 23 | double d = pos.dot(A); |
| 24 | double ts[3]; |
| 25 | int roots = cubicRootsValidT(a, b, c, d, ts); |
| 26 | double d0 = pt.distanceSquared(quad[0]); |
| 27 | double d2 = pt.distanceSquared(quad[2]); |
| 28 | double distMin = SkTMin(d0, d2); |
| 29 | int bestIndex = -1; |
| 30 | for (int index = 0; index < roots; ++index) { |
| 31 | _Point onQuad; |
| 32 | xy_at_t(quad, ts[index], onQuad.x, onQuad.y); |
| 33 | double dist = pt.distanceSquared(onQuad); |
| 34 | if (distMin > dist) { |
| 35 | distMin = dist; |
| 36 | bestIndex = index; |
| 37 | } |
| 38 | } |
| 39 | if (bestIndex >= 0) { |
| 40 | return ts[bestIndex]; |
| 41 | } |
| 42 | return d0 < d2 ? 0 : 1; |
| 43 | } |
| 44 | |
| 45 | bool point_in_hull(const Quadratic& quad, const _Point& pt) { |
| 46 | return pointInTriangle((const Triangle&) quad, pt); |
| 47 | } |
caryclark@google.com | 27accef | 2012-01-25 18:57:23 +0000 | [diff] [blame] | 48 | |
caryclark@google.com | 45a8fc6 | 2013-02-14 15:29:11 +0000 | [diff] [blame] | 49 | _Point top(const Quadratic& quad, double startT, double endT) { |
| 50 | Quadratic sub; |
| 51 | sub_divide(quad, startT, endT, sub); |
| 52 | _Point topPt = sub[0]; |
| 53 | if (topPt.y > sub[2].y || (topPt.y == sub[2].y && topPt.x > sub[2].x)) { |
| 54 | topPt = sub[2]; |
| 55 | } |
| 56 | if (!between(sub[0].y, sub[1].y, sub[2].y)) { |
| 57 | double extremeT; |
| 58 | if (findExtrema(sub[0].y, sub[1].y, sub[2].y, &extremeT)) { |
| 59 | extremeT = startT + (endT - startT) * extremeT; |
| 60 | _Point test; |
| 61 | xy_at_t(quad, extremeT, test.x, test.y); |
| 62 | if (topPt.y > test.y || (topPt.y == test.y && topPt.x > test.x)) { |
| 63 | topPt = test; |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | return topPt; |
| 68 | } |
| 69 | |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 70 | /* |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 71 | Numeric Solutions (5.6) suggests to solve the quadratic by computing |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 72 | Q = -1/2(B + sgn(B)Sqrt(B^2 - 4 A C)) |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 73 | and using the roots |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 74 | t1 = Q / A |
| 75 | t2 = C / Q |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 76 | */ |
caryclark@google.com | 9f60291 | 2013-01-24 21:47:16 +0000 | [diff] [blame] | 77 | int add_valid_ts(double s[], int realRoots, double* t) { |
| 78 | int foundRoots = 0; |
| 79 | for (int index = 0; index < realRoots; ++index) { |
| 80 | double tValue = s[index]; |
| 81 | if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) { |
| 82 | if (approximately_less_than_zero(tValue)) { |
| 83 | tValue = 0; |
| 84 | } else if (approximately_greater_than_one(tValue)) { |
| 85 | tValue = 1; |
| 86 | } |
| 87 | for (int idx2 = 0; idx2 < foundRoots; ++idx2) { |
| 88 | if (approximately_equal(t[idx2], tValue)) { |
| 89 | goto nextRoot; |
| 90 | } |
| 91 | } |
| 92 | t[foundRoots++] = tValue; |
| 93 | } |
| 94 | nextRoot: |
| 95 | ; |
| 96 | } |
| 97 | return foundRoots; |
| 98 | } |
| 99 | |
caryclark@google.com | a7e483d | 2012-08-28 20:44:43 +0000 | [diff] [blame] | 100 | // note: caller expects multiple results to be sorted smaller first |
| 101 | // note: http://en.wikipedia.org/wiki/Loss_of_significance has an interesting |
| 102 | // analysis of the quadratic equation, suggesting why the following looks at |
| 103 | // the sign of B -- and further suggesting that the greatest loss of precision |
| 104 | // is in b squared less two a c |
caryclark@google.com | 9f60291 | 2013-01-24 21:47:16 +0000 | [diff] [blame] | 105 | int quadraticRootsValidT(double A, double B, double C, double t[2]) { |
| 106 | #if 0 |
caryclark@google.com | 27accef | 2012-01-25 18:57:23 +0000 | [diff] [blame] | 107 | B *= 2; |
| 108 | double square = B * B - 4 * A * C; |
caryclark@google.com | 235f56a | 2012-09-14 14:19:30 +0000 | [diff] [blame] | 109 | if (approximately_negative(square)) { |
| 110 | if (!approximately_positive(square)) { |
| 111 | return 0; |
| 112 | } |
| 113 | square = 0; |
caryclark@google.com | 27accef | 2012-01-25 18:57:23 +0000 | [diff] [blame] | 114 | } |
| 115 | double squareRt = sqrt(square); |
| 116 | double Q = (B + (B < 0 ? -squareRt : squareRt)) / -2; |
| 117 | int foundRoots = 0; |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 118 | double ratio = Q / A; |
caryclark@google.com | a7e483d | 2012-08-28 20:44:43 +0000 | [diff] [blame] | 119 | if (approximately_zero_or_more(ratio) && approximately_one_or_less(ratio)) { |
| 120 | if (approximately_less_than_zero(ratio)) { |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 121 | ratio = 0; |
caryclark@google.com | a7e483d | 2012-08-28 20:44:43 +0000 | [diff] [blame] | 122 | } else if (approximately_greater_than_one(ratio)) { |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 123 | ratio = 1; |
caryclark@google.com | 78e1713 | 2012-04-17 11:40:34 +0000 | [diff] [blame] | 124 | } |
caryclark@google.com | a7e483d | 2012-08-28 20:44:43 +0000 | [diff] [blame] | 125 | t[0] = ratio; |
| 126 | ++foundRoots; |
caryclark@google.com | 27accef | 2012-01-25 18:57:23 +0000 | [diff] [blame] | 127 | } |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 128 | ratio = C / Q; |
caryclark@google.com | a7e483d | 2012-08-28 20:44:43 +0000 | [diff] [blame] | 129 | if (approximately_zero_or_more(ratio) && approximately_one_or_less(ratio)) { |
| 130 | if (approximately_less_than_zero(ratio)) { |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 131 | ratio = 0; |
caryclark@google.com | a7e483d | 2012-08-28 20:44:43 +0000 | [diff] [blame] | 132 | } else if (approximately_greater_than_one(ratio)) { |
caryclark@google.com | 03f9706 | 2012-08-21 13:13:52 +0000 | [diff] [blame] | 133 | ratio = 1; |
caryclark@google.com | 78e1713 | 2012-04-17 11:40:34 +0000 | [diff] [blame] | 134 | } |
caryclark@google.com | a7e483d | 2012-08-28 20:44:43 +0000 | [diff] [blame] | 135 | if (foundRoots == 0 || !approximately_negative(ratio - t[0])) { |
caryclark@google.com | c899ad9 | 2012-08-23 15:24:42 +0000 | [diff] [blame] | 136 | t[foundRoots++] = ratio; |
caryclark@google.com | a7e483d | 2012-08-28 20:44:43 +0000 | [diff] [blame] | 137 | } else if (!approximately_negative(t[0] - ratio)) { |
| 138 | t[foundRoots++] = t[0]; |
| 139 | t[0] = ratio; |
caryclark@google.com | c899ad9 | 2012-08-23 15:24:42 +0000 | [diff] [blame] | 140 | } |
caryclark@google.com | 27accef | 2012-01-25 18:57:23 +0000 | [diff] [blame] | 141 | } |
caryclark@google.com | 9f60291 | 2013-01-24 21:47:16 +0000 | [diff] [blame] | 142 | #else |
| 143 | double s[2]; |
| 144 | int realRoots = quadraticRootsReal(A, B, C, s); |
| 145 | int foundRoots = add_valid_ts(s, realRoots, t); |
| 146 | #endif |
caryclark@google.com | 27accef | 2012-01-25 18:57:23 +0000 | [diff] [blame] | 147 | return foundRoots; |
| 148 | } |
caryclark@google.com | 8dcf114 | 2012-07-02 20:27:02 +0000 | [diff] [blame] | 149 | |
caryclark@google.com | 9f60291 | 2013-01-24 21:47:16 +0000 | [diff] [blame] | 150 | // unlike quadratic roots, this does not discard real roots <= 0 or >= 1 |
| 151 | int quadraticRootsReal(const double A, const double B, const double C, double s[2]) { |
caryclark@google.com | 85ec74c | 2013-01-28 19:25:51 +0000 | [diff] [blame] | 152 | const double p = B / (2 * A); |
| 153 | const double q = C / A; |
| 154 | if (approximately_zero(A) && (approximately_zero_inverse(p) || approximately_zero_inverse(q))) { |
caryclark@google.com | 9f60291 | 2013-01-24 21:47:16 +0000 | [diff] [blame] | 155 | if (approximately_zero(B)) { |
| 156 | s[0] = 0; |
| 157 | return C == 0; |
| 158 | } |
| 159 | s[0] = -C / B; |
| 160 | return 1; |
| 161 | } |
| 162 | /* normal form: x^2 + px + q = 0 */ |
caryclark@google.com | 9f60291 | 2013-01-24 21:47:16 +0000 | [diff] [blame] | 163 | const double p2 = p * p; |
| 164 | #if 0 |
| 165 | double D = AlmostEqualUlps(p2, q) ? 0 : p2 - q; |
| 166 | if (D <= 0) { |
| 167 | if (D < 0) { |
| 168 | return 0; |
| 169 | } |
| 170 | s[0] = -p; |
| 171 | SkDebugf("[%d] %1.9g\n", 1, s[0]); |
| 172 | return 1; |
| 173 | } |
| 174 | double sqrt_D = sqrt(D); |
| 175 | s[0] = sqrt_D - p; |
| 176 | s[1] = -sqrt_D - p; |
| 177 | SkDebugf("[%d] %1.9g %1.9g\n", 2, s[0], s[1]); |
| 178 | return 2; |
| 179 | #else |
| 180 | if (!AlmostEqualUlps(p2, q) && p2 < q) { |
| 181 | return 0; |
| 182 | } |
| 183 | double sqrt_D = 0; |
| 184 | if (p2 > q) { |
| 185 | sqrt_D = sqrt(p2 - q); |
| 186 | } |
| 187 | s[0] = sqrt_D - p; |
| 188 | s[1] = -sqrt_D - p; |
| 189 | #if 0 |
| 190 | if (AlmostEqualUlps(s[0], s[1])) { |
| 191 | SkDebugf("[%d] %1.9g\n", 1, s[0]); |
| 192 | } else { |
| 193 | SkDebugf("[%d] %1.9g %1.9g\n", 2, s[0], s[1]); |
| 194 | } |
| 195 | #endif |
| 196 | return 1 + !AlmostEqualUlps(s[0], s[1]); |
| 197 | #endif |
| 198 | } |
| 199 | |
caryclark@google.com | d0a19eb | 2013-02-19 12:49:33 +0000 | [diff] [blame] | 200 | void toCubic(const Quadratic& quad, Cubic& cubic) { |
| 201 | cubic[0] = quad[0]; |
| 202 | cubic[2] = quad[1]; |
| 203 | cubic[3] = quad[2]; |
| 204 | cubic[1].x = (cubic[0].x + cubic[2].x * 2) / 3; |
| 205 | cubic[1].y = (cubic[0].y + cubic[2].y * 2) / 3; |
| 206 | cubic[2].x = (cubic[3].x + cubic[2].x * 2) / 3; |
| 207 | cubic[2].y = (cubic[3].y + cubic[2].y * 2) / 3; |
| 208 | } |
| 209 | |
caryclark@google.com | 05c4bad | 2013-01-19 13:22:39 +0000 | [diff] [blame] | 210 | static double derivativeAtT(const double* quad, double t) { |
caryclark@google.com | 8dcf114 | 2012-07-02 20:27:02 +0000 | [diff] [blame] | 211 | double a = t - 1; |
| 212 | double b = 1 - 2 * t; |
| 213 | double c = t; |
caryclark@google.com | 05c4bad | 2013-01-19 13:22:39 +0000 | [diff] [blame] | 214 | return a * quad[0] + b * quad[2] + c * quad[4]; |
| 215 | } |
| 216 | |
| 217 | double dx_at_t(const Quadratic& quad, double t) { |
| 218 | return derivativeAtT(&quad[0].x, t); |
| 219 | } |
| 220 | |
| 221 | double dy_at_t(const Quadratic& quad, double t) { |
| 222 | return derivativeAtT(&quad[0].y, t); |
| 223 | } |
| 224 | |
caryclark@google.com | 7ff5c84 | 2013-02-26 15:56:05 +0000 | [diff] [blame^] | 225 | _Vector dxdy_at_t(const Quadratic& quad, double t) { |
caryclark@google.com | 05c4bad | 2013-01-19 13:22:39 +0000 | [diff] [blame] | 226 | double a = t - 1; |
| 227 | double b = 1 - 2 * t; |
| 228 | double c = t; |
caryclark@google.com | 7ff5c84 | 2013-02-26 15:56:05 +0000 | [diff] [blame^] | 229 | _Vector result = { a * quad[0].x + b * quad[1].x + c * quad[2].x, |
| 230 | a * quad[0].y + b * quad[1].y + c * quad[2].y }; |
| 231 | return result; |
caryclark@google.com | 8dcf114 | 2012-07-02 20:27:02 +0000 | [diff] [blame] | 232 | } |
| 233 | |
| 234 | void xy_at_t(const Quadratic& quad, double t, double& x, double& y) { |
| 235 | double one_t = 1 - t; |
| 236 | double a = one_t * one_t; |
| 237 | double b = 2 * one_t * t; |
| 238 | double c = t * t; |
| 239 | if (&x) { |
| 240 | x = a * quad[0].x + b * quad[1].x + c * quad[2].x; |
| 241 | } |
| 242 | if (&y) { |
| 243 | y = a * quad[0].y + b * quad[1].y + c * quad[2].y; |
| 244 | } |
| 245 | } |
caryclark@google.com | 47d73da | 2013-02-17 01:41:25 +0000 | [diff] [blame] | 246 | |
| 247 | _Point xy_at_t(const Quadratic& quad, double t) { |
| 248 | double one_t = 1 - t; |
| 249 | double a = one_t * one_t; |
| 250 | double b = 2 * one_t * t; |
| 251 | double c = t * t; |
| 252 | _Point result = { a * quad[0].x + b * quad[1].x + c * quad[2].x, |
| 253 | a * quad[0].y + b * quad[1].y + c * quad[2].y }; |
| 254 | return result; |
| 255 | } |