blob: eb2ddb7c7d725cdc79871f7d6a9a9a1036379893 [file] [log] [blame]
caryclark@google.com07393ca2013-04-08 11:47:37 +00001/*
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 */
caryclark54359292015-03-26 07:52:43 -07007#include "SkGeometry.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +00008#include "SkLineParameters.h"
caryclark1049f122015-04-20 08:31:59 -07009#include "SkPathOpsConic.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +000010#include "SkPathOpsCubic.h"
caryclark03b03ca2015-04-23 09:13:37 -070011#include "SkPathOpsCurve.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +000012#include "SkPathOpsLine.h"
13#include "SkPathOpsQuad.h"
14#include "SkPathOpsRect.h"
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000015#include "SkTSort.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +000016
17const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework
18
caryclark624637c2015-05-11 07:21:27 -070019void SkDCubic::align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const {
20 if (fPts[endIndex].fX == fPts[ctrlIndex].fX) {
21 dstPt->fX = fPts[endIndex].fX;
22 }
23 if (fPts[endIndex].fY == fPts[ctrlIndex].fY) {
24 dstPt->fY = fPts[endIndex].fY;
25 }
26}
27
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000028// give up when changing t no longer moves point
29// also, copy point rather than recompute it when it does change
30double SkDCubic::binarySearch(double min, double max, double axisIntercept,
31 SearchAxis xAxis) const {
32 double t = (min + max) / 2;
33 double step = (t - min) / 2;
34 SkDPoint cubicAtT = ptAtT(t);
35 double calcPos = (&cubicAtT.fX)[xAxis];
36 double calcDist = calcPos - axisIntercept;
37 do {
Cary Clark74b42902018-03-09 07:38:47 -050038 double priorT = std::max(min, t - step);
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000039 SkDPoint lessPt = ptAtT(priorT);
caryclark54359292015-03-26 07:52:43 -070040 if (approximately_equal_half(lessPt.fX, cubicAtT.fX)
41 && approximately_equal_half(lessPt.fY, cubicAtT.fY)) {
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000042 return -1; // binary search found no point at this axis intercept
43 }
44 double lessDist = (&lessPt.fX)[xAxis] - axisIntercept;
45#if DEBUG_CUBIC_BINARY_SEARCH
46 SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist,
47 step, lessDist);
48#endif
49 double lastStep = step;
50 step /= 2;
51 if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) {
52 t = priorT;
53 } else {
54 double nextT = t + lastStep;
caryclark54359292015-03-26 07:52:43 -070055 if (nextT > max) {
56 return -1;
57 }
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000058 SkDPoint morePt = ptAtT(nextT);
caryclark54359292015-03-26 07:52:43 -070059 if (approximately_equal_half(morePt.fX, cubicAtT.fX)
60 && approximately_equal_half(morePt.fY, cubicAtT.fY)) {
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000061 return -1; // binary search found no point at this axis intercept
62 }
63 double moreDist = (&morePt.fX)[xAxis] - axisIntercept;
64 if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) {
65 continue;
66 }
67 t = nextT;
68 }
69 SkDPoint testAtT = ptAtT(t);
70 cubicAtT = testAtT;
71 calcPos = (&cubicAtT.fX)[xAxis];
72 calcDist = calcPos - axisIntercept;
73 } while (!approximately_equal(calcPos, axisIntercept));
74 return t;
75}
76
Cary Clark7eb01e02016-12-08 14:36:32 -050077// get the rough scale of the cubic; used to determine if curvature is extreme
caryclark@google.com07393ca2013-04-08 11:47:37 +000078double SkDCubic::calcPrecision() const {
Cary Clark7eb01e02016-12-08 14:36:32 -050079 return ((fPts[1] - fPts[0]).length()
80 + (fPts[2] - fPts[1]).length()
81 + (fPts[3] - fPts[2]).length()) / gPrecisionUnit;
caryclark@google.com07393ca2013-04-08 11:47:37 +000082}
83
caryclark624637c2015-05-11 07:21:27 -070084/* classic one t subdivision */
85static void interp_cubic_coords(const double* src, double* dst, double t) {
86 double ab = SkDInterp(src[0], src[2], t);
87 double bc = SkDInterp(src[2], src[4], t);
88 double cd = SkDInterp(src[4], src[6], t);
89 double abc = SkDInterp(ab, bc, t);
90 double bcd = SkDInterp(bc, cd, t);
91 double abcd = SkDInterp(abc, bcd, t);
92
93 dst[0] = src[0];
94 dst[2] = ab;
95 dst[4] = abc;
96 dst[6] = abcd;
97 dst[8] = bcd;
98 dst[10] = cd;
99 dst[12] = src[6];
caryclark@google.com07393ca2013-04-08 11:47:37 +0000100}
101
caryclark624637c2015-05-11 07:21:27 -0700102SkDCubicPair SkDCubic::chopAt(double t) const {
103 SkDCubicPair dst;
104 if (t == 0.5) {
105 dst.pts[0] = fPts[0];
106 dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2;
107 dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2;
108 dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4;
109 dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4;
110 dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8;
111 dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8;
112 dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4;
113 dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4;
114 dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2;
115 dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2;
116 dst.pts[6] = fPts[3];
117 return dst;
118 }
119 interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t);
120 interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t);
121 return dst;
caryclark03b03ca2015-04-23 09:13:37 -0700122}
123
caryclark@google.com07393ca2013-04-08 11:47:37 +0000124void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) {
125 *A = src[6]; // d
126 *B = src[4] * 3; // 3*c
127 *C = src[2] * 3; // 3*b
128 *D = src[0]; // a
129 *A -= *D - *C + *B; // A = -a + 3*b - 3*c + d
130 *B += 3 * *D - 2 * *C; // B = 3*a - 6*b + 3*c
131 *C -= 3 * *D; // C = -3*a + 3*b
132}
133
caryclark@google.com07393ca2013-04-08 11:47:37 +0000134bool SkDCubic::endsAreExtremaInXOrY() const {
135 return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
136 && between(fPts[0].fX, fPts[2].fX, fPts[3].fX))
137 || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
138 && between(fPts[0].fY, fPts[2].fY, fPts[3].fY));
139}
140
caryclark54359292015-03-26 07:52:43 -0700141// Do a quick reject by rotating all points relative to a line formed by
142// a pair of one cubic's points. If the 2nd cubic's points
143// are on the line or on the opposite side from the 1st cubic's 'odd man', the
144// curves at most intersect at the endpoints.
145/* if returning true, check contains true if cubic's hull collapsed, making the cubic linear
146 if returning false, check contains true if the the cubic pair have only the end point in common
147*/
caryclark1049f122015-04-20 08:31:59 -0700148bool SkDCubic::hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const {
caryclark54359292015-03-26 07:52:43 -0700149 bool linear = true;
150 char hullOrder[4];
151 int hullCount = convexHull(hullOrder);
152 int end1 = hullOrder[0];
153 int hullIndex = 0;
154 const SkDPoint* endPt[2];
155 endPt[0] = &fPts[end1];
156 do {
157 hullIndex = (hullIndex + 1) % hullCount;
158 int end2 = hullOrder[hullIndex];
159 endPt[1] = &fPts[end2];
160 double origX = endPt[0]->fX;
161 double origY = endPt[0]->fY;
162 double adj = endPt[1]->fX - origX;
163 double opp = endPt[1]->fY - origY;
164 int oddManMask = other_two(end1, end2);
165 int oddMan = end1 ^ oddManMask;
166 double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp;
167 int oddMan2 = end2 ^ oddManMask;
168 double sign2 = (fPts[oddMan2].fY - origY) * adj - (fPts[oddMan2].fX - origX) * opp;
169 if (sign * sign2 < 0) {
170 continue;
171 }
172 if (approximately_zero(sign)) {
173 sign = sign2;
174 if (approximately_zero(sign)) {
175 continue;
176 }
177 }
178 linear = false;
179 bool foundOutlier = false;
caryclark1049f122015-04-20 08:31:59 -0700180 for (int n = 0; n < ptCount; ++n) {
181 double test = (pts[n].fY - origY) * adj - (pts[n].fX - origX) * opp;
caryclark54359292015-03-26 07:52:43 -0700182 if (test * sign > 0 && !precisely_zero(test)) {
183 foundOutlier = true;
184 break;
185 }
186 }
187 if (!foundOutlier) {
188 return false;
189 }
190 endPt[0] = endPt[1];
191 end1 = end2;
192 } while (hullIndex);
193 *isLinear = linear;
194 return true;
195}
196
caryclark1049f122015-04-20 08:31:59 -0700197bool SkDCubic::hullIntersects(const SkDCubic& c2, bool* isLinear) const {
198 return hullIntersects(c2.fPts, c2.kPointCount, isLinear);
199}
200
201bool SkDCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const {
202 return hullIntersects(quad.fPts, quad.kPointCount, isLinear);
203}
204
205bool SkDCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const {
206
207 return hullIntersects(conic.fPts, isLinear);
208}
209
caryclark@google.com07393ca2013-04-08 11:47:37 +0000210bool SkDCubic::isLinear(int startIndex, int endIndex) const {
caryclarke3a4e992016-09-28 09:22:17 -0700211 if (fPts[0].approximatelyDEqual(fPts[3])) {
212 return ((const SkDQuad *) this)->isLinear(0, 2);
213 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000214 SkLineParameters lineParameters;
215 lineParameters.cubicEndPoints(*this, startIndex, endIndex);
216 // FIXME: maybe it's possible to avoid this and compare non-normalized
217 lineParameters.normalize();
caryclark54359292015-03-26 07:52:43 -0700218 double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY),
219 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
220 double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY),
221 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
222 largest = SkTMax(largest, -tiniest);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000223 double distance = lineParameters.controlPtDistance(*this, 1);
caryclark54359292015-03-26 07:52:43 -0700224 if (!approximately_zero_when_compared_to(distance, largest)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000225 return false;
226 }
227 distance = lineParameters.controlPtDistance(*this, 2);
caryclark54359292015-03-26 07:52:43 -0700228 return approximately_zero_when_compared_to(distance, largest);
229}
230
Cary Clark7eb01e02016-12-08 14:36:32 -0500231// from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf
232// c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3
233// c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2
234// = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2
235static double derivative_at_t(const double* src, double t) {
236 double one_t = 1 - t;
237 double a = src[0];
238 double b = src[2];
239 double c = src[4];
240 double d = src[6];
241 return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
242}
243
244int SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) {
245 SkDCubic cubic;
246 cubic.set(pointsPtr);
247 if (cubic.monotonicInX() && cubic.monotonicInY()) {
248 return 0;
249 }
Chris Dalton390f6cd2017-06-12 11:22:54 -0600250 double tt[2], ss[2];
Chris Daltonfebbffa2017-06-08 13:12:02 -0600251 SkCubicType cubicType = SkClassifyCubic(pointsPtr, tt, ss);
Cary Clark7eb01e02016-12-08 14:36:32 -0500252 switch (cubicType) {
Chris Dalton43436542017-04-13 14:26:00 -0600253 case SkCubicType::kLoop: {
Chris Dalton390f6cd2017-06-12 11:22:54 -0600254 const double &td = tt[0], &te = tt[1], &sd = ss[0], &se = ss[1];
Christopher Dalton7f5af0c2017-06-06 14:26:27 -0600255 if (roughly_between(0, td, sd) && roughly_between(0, te, se)) {
Chris Daltonfebbffa2017-06-08 13:12:02 -0600256 SkASSERT(roughly_between(0, td/sd, 1) && roughly_between(0, te/se, 1));
Chris Dalton390f6cd2017-06-12 11:22:54 -0600257 t[0] = static_cast<SkScalar>((td * se + te * sd) / (2 * sd * se));
Cary Clark7eb01e02016-12-08 14:36:32 -0500258 SkASSERT(roughly_between(0, *t, 1));
259 return (int) (t[0] > 0 && t[0] < 1);
260 }
caryclark54359292015-03-26 07:52:43 -0700261 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500262 // fall through if no t value found
Chris Dalton43436542017-04-13 14:26:00 -0600263 case SkCubicType::kSerpentine:
264 case SkCubicType::kLocalCusp:
Chris Daltonfebbffa2017-06-08 13:12:02 -0600265 case SkCubicType::kCuspAtInfinity: {
Cary Clark7eb01e02016-12-08 14:36:32 -0500266 double inflectionTs[2];
267 int infTCount = cubic.findInflections(inflectionTs);
caryclark54359292015-03-26 07:52:43 -0700268 double maxCurvature[3];
269 int roots = cubic.findMaxCurvature(maxCurvature);
Cary Clark7eb01e02016-12-08 14:36:32 -0500270 #if DEBUG_CUBIC_SPLIT
caryclark03b03ca2015-04-23 09:13:37 -0700271 SkDebugf("%s\n", __FUNCTION__);
272 cubic.dump();
273 for (int index = 0; index < infTCount; ++index) {
274 SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]);
275 SkDPoint pt = cubic.ptAtT(inflectionTs[index]);
276 SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]);
277 SkDLine perp = {{pt - dPt, pt + dPt}};
278 perp.dump();
279 }
280 for (int index = 0; index < roots; ++index) {
281 SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]);
282 SkDPoint pt = cubic.ptAtT(maxCurvature[index]);
283 SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]);
284 SkDLine perp = {{pt - dPt, pt + dPt}};
285 perp.dump();
286 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500287 #endif
288 if (infTCount == 2) {
289 for (int index = 0; index < roots; ++index) {
290 if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) {
291 t[0] = maxCurvature[index];
292 return (int) (t[0] > 0 && t[0] < 1);
293 }
caryclark54359292015-03-26 07:52:43 -0700294 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500295 } else {
296 int resultCount = 0;
297 // FIXME: constant found through experimentation -- maybe there's a better way....
298 double precision = cubic.calcPrecision() * 2;
299 for (int index = 0; index < roots; ++index) {
300 double testT = maxCurvature[index];
301 if (0 >= testT || testT >= 1) {
302 continue;
303 }
304 // don't call dxdyAtT since we want (0,0) results
305 SkDVector dPt = { derivative_at_t(&cubic.fPts[0].fX, testT),
306 derivative_at_t(&cubic.fPts[0].fY, testT) };
307 double dPtLen = dPt.length();
308 if (dPtLen < precision) {
309 t[resultCount++] = testT;
310 }
311 }
312 if (!resultCount && infTCount == 1) {
313 t[0] = inflectionTs[0];
314 resultCount = (int) (t[0] > 0 && t[0] < 1);
315 }
316 return resultCount;
caryclark54359292015-03-26 07:52:43 -0700317 }
caryclark54359292015-03-26 07:52:43 -0700318 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500319 default:
320 ;
caryclark54359292015-03-26 07:52:43 -0700321 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500322 return 0;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000323}
324
caryclarkaec25102015-04-29 08:28:30 -0700325bool SkDCubic::monotonicInX() const {
326 return precisely_between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
327 && precisely_between(fPts[0].fX, fPts[2].fX, fPts[3].fX);
328}
329
caryclark@google.com07393ca2013-04-08 11:47:37 +0000330bool SkDCubic::monotonicInY() const {
caryclarkaec25102015-04-29 08:28:30 -0700331 return precisely_between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
332 && precisely_between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000333}
334
caryclark54359292015-03-26 07:52:43 -0700335void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const {
336 int offset = (int) !SkToBool(index);
337 o1Pts[0] = &fPts[offset];
338 o1Pts[1] = &fPts[++offset];
339 o1Pts[2] = &fPts[++offset];
340}
341
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000342int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept,
343 SearchAxis xAxis, double* validRoots) const {
344 extrema += findInflections(&extremeTs[extrema]);
345 extremeTs[extrema++] = 0;
346 extremeTs[extrema] = 1;
caryclark8a8accb2016-07-22 10:56:26 -0700347 SkASSERT(extrema < 6);
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000348 SkTQSort(extremeTs, extremeTs + extrema);
349 int validCount = 0;
350 for (int index = 0; index < extrema; ) {
351 double min = extremeTs[index];
352 double max = extremeTs[++index];
353 if (min == max) {
354 continue;
355 }
356 double newT = binarySearch(min, max, axisIntercept, xAxis);
357 if (newT >= 0) {
caryclark8a8accb2016-07-22 10:56:26 -0700358 if (validCount >= 3) {
359 return 0;
360 }
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000361 validRoots[validCount++] = newT;
362 }
363 }
364 return validCount;
365}
366
caryclark@google.com07393ca2013-04-08 11:47:37 +0000367// cubic roots
368
369static const double PI = 3.141592653589793;
370
371// from SkGeometry.cpp (and Numeric Solutions, 5.6)
372int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) {
373 double s[3];
374 int realRoots = RootsReal(A, B, C, D, s);
375 int foundRoots = SkDQuad::AddValidTs(s, realRoots, t);
caryclarkaec25102015-04-29 08:28:30 -0700376 for (int index = 0; index < realRoots; ++index) {
377 double tValue = s[index];
378 if (!approximately_one_or_less(tValue) && between(1, tValue, 1.00005)) {
379 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
380 if (approximately_equal(t[idx2], 1)) {
381 goto nextRoot;
382 }
383 }
384 SkASSERT(foundRoots < 3);
385 t[foundRoots++] = 1;
386 } else if (!approximately_zero_or_more(tValue) && between(-0.00005, tValue, 0)) {
387 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
388 if (approximately_equal(t[idx2], 0)) {
389 goto nextRoot;
390 }
391 }
392 SkASSERT(foundRoots < 3);
393 t[foundRoots++] = 0;
394 }
395nextRoot:
396 ;
397 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000398 return foundRoots;
399}
400
401int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
402#ifdef SK_DEBUG
403 // create a string mathematica understands
404 // GDB set print repe 15 # if repeated digits is a bother
405 // set print elements 400 # if line doesn't fit
406 char str[1024];
407 sk_bzero(str, sizeof(str));
408 SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
409 A, B, C, D);
caryclark@google.com570863f2013-09-16 15:55:01 +0000410 SkPathOpsDebug::MathematicaIze(str, sizeof(str));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000411#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
412 SkDebugf("%s\n", str);
413#endif
414#endif
415 if (approximately_zero(A)
416 && approximately_zero_when_compared_to(A, B)
417 && approximately_zero_when_compared_to(A, C)
418 && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic
419 return SkDQuad::RootsReal(B, C, D, s);
420 }
421 if (approximately_zero_when_compared_to(D, A)
422 && approximately_zero_when_compared_to(D, B)
423 && approximately_zero_when_compared_to(D, C)) { // 0 is one root
424 int num = SkDQuad::RootsReal(A, B, C, s);
425 for (int i = 0; i < num; ++i) {
426 if (approximately_zero(s[i])) {
427 return num;
428 }
429 }
430 s[num++] = 0;
431 return num;
432 }
433 if (approximately_zero(A + B + C + D)) { // 1 is one root
434 int num = SkDQuad::RootsReal(A, A + B, -D, s);
435 for (int i = 0; i < num; ++i) {
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000436 if (AlmostDequalUlps(s[i], 1)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000437 return num;
438 }
439 }
440 s[num++] = 1;
441 return num;
442 }
443 double a, b, c;
444 {
445 double invA = 1 / A;
446 a = B * invA;
447 b = C * invA;
448 c = D * invA;
449 }
450 double a2 = a * a;
451 double Q = (a2 - b * 3) / 9;
452 double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
453 double R2 = R * R;
454 double Q3 = Q * Q * Q;
455 double R2MinusQ3 = R2 - Q3;
456 double adiv3 = a / 3;
457 double r;
458 double* roots = s;
459 if (R2MinusQ3 < 0) { // we have 3 real roots
caryclark93ca8842016-05-27 05:24:37 -0700460 // the divide/root can, due to finite precisions, be slightly outside of -1...1
461 double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000462 double neg2RootQ = -2 * sqrt(Q);
463
464 r = neg2RootQ * cos(theta / 3) - adiv3;
465 *roots++ = r;
466
467 r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000468 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000469 *roots++ = r;
470 }
471 r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000472 if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000473 *roots++ = r;
474 }
475 } else { // we have 1 real root
476 double sqrtR2MinusQ3 = sqrt(R2MinusQ3);
477 double A = fabs(R) + sqrtR2MinusQ3;
478 A = SkDCubeRoot(A);
479 if (R > 0) {
480 A = -A;
481 }
482 if (A != 0) {
483 A += Q / A;
484 }
485 r = A - adiv3;
486 *roots++ = r;
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000487 if (AlmostDequalUlps((double) R2, (double) Q3)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000488 r = -A / 2 - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000489 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000490 *roots++ = r;
491 }
492 }
493 }
494 return static_cast<int>(roots - s);
495}
496
caryclark@google.com07393ca2013-04-08 11:47:37 +0000497// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
498SkDVector SkDCubic::dxdyAtT(double t) const {
499 SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
caryclark94c902e2015-08-18 07:12:43 -0700500 if (result.fX == 0 && result.fY == 0) {
501 if (t == 0) {
502 result = fPts[2] - fPts[0];
503 } else if (t == 1) {
504 result = fPts[3] - fPts[1];
505 } else {
506 // incomplete
507 SkDebugf("!c");
508 }
509 if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) {
510 result = fPts[3] - fPts[0];
511 }
512 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000513 return result;
514}
515
516// OPTIMIZE? share code with formulate_F1DotF2
517int SkDCubic::findInflections(double tValues[]) const {
518 double Ax = fPts[1].fX - fPts[0].fX;
519 double Ay = fPts[1].fY - fPts[0].fY;
520 double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX;
521 double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY;
522 double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX;
523 double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY;
524 return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues);
525}
526
527static void formulate_F1DotF2(const double src[], double coeff[4]) {
528 double a = src[2] - src[0];
529 double b = src[4] - 2 * src[2] + src[0];
530 double c = src[6] + 3 * (src[2] - src[4]) - src[0];
531 coeff[0] = c * c;
532 coeff[1] = 3 * b * c;
533 coeff[2] = 2 * b * b + c * a;
534 coeff[3] = a * b;
535}
536
537/** SkDCubic'(t) = At^2 + Bt + C, where
538 A = 3(-a + 3(b - c) + d)
539 B = 6(a - 2b + c)
540 C = 3(b - a)
541 Solve for t, keeping only those that fit between 0 < t < 1
542*/
caryclarkaec25102015-04-29 08:28:30 -0700543int SkDCubic::FindExtrema(const double src[], double tValues[2]) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000544 // we divide A,B,C by 3 to simplify
caryclarkaec25102015-04-29 08:28:30 -0700545 double a = src[0];
546 double b = src[2];
547 double c = src[4];
548 double d = src[6];
549 double A = d - a + 3 * (b - c);
550 double B = 2 * (a - b - b + c);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000551 double C = b - a;
552
553 return SkDQuad::RootsValidT(A, B, C, tValues);
554}
555
556/* from SkGeometry.cpp
557 Looking for F' dot F'' == 0
558
559 A = b - a
560 B = c - 2b + a
561 C = d - 3c + 3b - a
562
563 F' = 3Ct^2 + 6Bt + 3A
564 F'' = 6Ct + 6B
565
566 F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
567*/
568int SkDCubic::findMaxCurvature(double tValues[]) const {
569 double coeffX[4], coeffY[4];
570 int i;
571 formulate_F1DotF2(&fPts[0].fX, coeffX);
572 formulate_F1DotF2(&fPts[0].fY, coeffY);
573 for (i = 0; i < 4; i++) {
574 coeffX[i] = coeffX[i] + coeffY[i];
575 }
576 return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues);
577}
578
caryclark@google.com4fdbb222013-07-23 15:27:41 +0000579SkDPoint SkDCubic::ptAtT(double t) const {
580 if (0 == t) {
581 return fPts[0];
582 }
583 if (1 == t) {
584 return fPts[3];
585 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000586 double one_t = 1 - t;
587 double one_t2 = one_t * one_t;
588 double a = one_t2 * one_t;
589 double b = 3 * one_t2 * t;
590 double t2 = t * t;
591 double c = 3 * one_t * t2;
592 double d = t2 * t;
593 SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX,
594 a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY};
595 return result;
596}
597
598/*
599 Given a cubic c, t1, and t2, find a small cubic segment.
600
601 The new cubic is defined as points A, B, C, and D, where
602 s1 = 1 - t1
603 s2 = 1 - t2
604 A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1
605 D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2
606
607 We don't have B or C. So We define two equations to isolate them.
608 First, compute two reference T values 1/3 and 2/3 from t1 to t2:
609
610 c(at (2*t1 + t2)/3) == E
611 c(at (t1 + 2*t2)/3) == F
612
613 Next, compute where those values must be if we know the values of B and C:
614
615 _12 = A*2/3 + B*1/3
616 12_ = A*1/3 + B*2/3
617 _23 = B*2/3 + C*1/3
618 23_ = B*1/3 + C*2/3
619 _34 = C*2/3 + D*1/3
620 34_ = C*1/3 + D*2/3
621 _123 = (A*2/3 + B*1/3)*2/3 + (B*2/3 + C*1/3)*1/3 = A*4/9 + B*4/9 + C*1/9
622 123_ = (A*1/3 + B*2/3)*1/3 + (B*1/3 + C*2/3)*2/3 = A*1/9 + B*4/9 + C*4/9
623 _234 = (B*2/3 + C*1/3)*2/3 + (C*2/3 + D*1/3)*1/3 = B*4/9 + C*4/9 + D*1/9
624 234_ = (B*1/3 + C*2/3)*1/3 + (C*1/3 + D*2/3)*2/3 = B*1/9 + C*4/9 + D*4/9
625 _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3
626 = A*8/27 + B*12/27 + C*6/27 + D*1/27
627 = E
628 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3
629 = A*1/27 + B*6/27 + C*12/27 + D*8/27
630 = F
631 E*27 = A*8 + B*12 + C*6 + D
632 F*27 = A + B*6 + C*12 + D*8
633
634Group the known values on one side:
635
636 M = E*27 - A*8 - D = B*12 + C* 6
637 N = F*27 - A - D*8 = B* 6 + C*12
638 M*2 - N = B*18
639 N*2 - M = C*18
640 B = (M*2 - N)/18
641 C = (N*2 - M)/18
642 */
643
644static double interp_cubic_coords(const double* src, double t) {
645 double ab = SkDInterp(src[0], src[2], t);
646 double bc = SkDInterp(src[2], src[4], t);
647 double cd = SkDInterp(src[4], src[6], t);
648 double abc = SkDInterp(ab, bc, t);
649 double bcd = SkDInterp(bc, cd, t);
650 double abcd = SkDInterp(abc, bcd, t);
651 return abcd;
652}
653
654SkDCubic SkDCubic::subDivide(double t1, double t2) const {
caryclark@google.comd892bd82013-06-17 14:10:36 +0000655 if (t1 == 0 || t2 == 1) {
656 if (t1 == 0 && t2 == 1) {
657 return *this;
658 }
659 SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1);
660 SkDCubic dst = t1 == 0 ? pair.first() : pair.second();
661 return dst;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000662 }
663 SkDCubic dst;
664 double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1);
665 double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1);
666 double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3);
667 double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3);
668 double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3);
669 double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3);
670 double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2);
671 double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2);
672 double mx = ex * 27 - ax * 8 - dx;
673 double my = ey * 27 - ay * 8 - dy;
674 double nx = fx * 27 - ax - dx * 8;
675 double ny = fy * 27 - ay - dy * 8;
676 /* bx = */ dst[1].fX = (mx * 2 - nx) / 18;
677 /* by = */ dst[1].fY = (my * 2 - ny) / 18;
678 /* cx = */ dst[2].fX = (nx * 2 - mx) / 18;
679 /* cy = */ dst[2].fY = (ny * 2 - my) / 18;
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000680 // FIXME: call align() ?
caryclark@google.com07393ca2013-04-08 11:47:37 +0000681 return dst;
682}
683
684void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
685 double t1, double t2, SkDPoint dst[2]) const {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000686 SkASSERT(t1 != t2);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000687 // this approach assumes that the control points computed directly are accurate enough
688 SkDCubic sub = subDivide(t1, t2);
689 dst[0] = sub[1] + (a - sub[0]);
690 dst[1] = sub[2] + (d - sub[3]);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000691 if (t1 == 0 || t2 == 0) {
692 align(0, 1, t1 == 0 ? &dst[0] : &dst[1]);
693 }
694 if (t1 == 1 || t2 == 1) {
695 align(3, 2, t1 == 1 ? &dst[0] : &dst[1]);
696 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000697 if (AlmostBequalUlps(dst[0].fX, a.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000698 dst[0].fX = a.fX;
699 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000700 if (AlmostBequalUlps(dst[0].fY, a.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000701 dst[0].fY = a.fY;
702 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000703 if (AlmostBequalUlps(dst[1].fX, d.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000704 dst[1].fX = d.fX;
705 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000706 if (AlmostBequalUlps(dst[1].fY, d.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000707 dst[1].fY = d.fY;
708 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000709}
710
Cary Clark7eb01e02016-12-08 14:36:32 -0500711bool SkDCubic::toFloatPoints(SkPoint* pts) const {
712 const double* dCubic = &fPts[0].fX;
713 SkScalar* cubic = &pts[0].fX;
714 for (int index = 0; index < kPointCount * 2; ++index) {
Cary Clarkafca4d62017-12-01 15:23:00 -0500715 cubic[index] = SkDoubleToScalar(dCubic[index]);
716 if (SkScalarAbs(cubic[index]) < FLT_EPSILON_ORDERABLE_ERR) {
717 cubic[index] = 0;
718 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500719 }
720 return SkScalarsAreFinite(&pts->fX, kPointCount * 2);
721}
722
caryclark624637c2015-05-11 07:21:27 -0700723double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const {
724 double extremeTs[2];
725 double topT = -1;
726 int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs);
727 for (int index = 0; index < roots; ++index) {
728 double t = startT + (endT - startT) * extremeTs[index];
729 SkDPoint mid = dCurve.ptAtT(t);
730 if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) {
731 topT = t;
732 *topPt = mid;
733 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000734 }
caryclark624637c2015-05-11 07:21:27 -0700735 return topT;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000736}