blob: 1e74eb0afd02bcb1dd5b40e2db649d72732a43ab [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 {
38 double priorT = t - step;
caryclarka35ab3e2016-10-20 08:32:18 -070039 SkOPASSERT(priorT >= min);
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000040 SkDPoint lessPt = ptAtT(priorT);
caryclark54359292015-03-26 07:52:43 -070041 if (approximately_equal_half(lessPt.fX, cubicAtT.fX)
42 && approximately_equal_half(lessPt.fY, cubicAtT.fY)) {
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000043 return -1; // binary search found no point at this axis intercept
44 }
45 double lessDist = (&lessPt.fX)[xAxis] - axisIntercept;
46#if DEBUG_CUBIC_BINARY_SEARCH
47 SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist,
48 step, lessDist);
49#endif
50 double lastStep = step;
51 step /= 2;
52 if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) {
53 t = priorT;
54 } else {
55 double nextT = t + lastStep;
caryclark54359292015-03-26 07:52:43 -070056 if (nextT > max) {
57 return -1;
58 }
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000059 SkDPoint morePt = ptAtT(nextT);
caryclark54359292015-03-26 07:52:43 -070060 if (approximately_equal_half(morePt.fX, cubicAtT.fX)
61 && approximately_equal_half(morePt.fY, cubicAtT.fY)) {
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000062 return -1; // binary search found no point at this axis intercept
63 }
64 double moreDist = (&morePt.fX)[xAxis] - axisIntercept;
65 if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) {
66 continue;
67 }
68 t = nextT;
69 }
70 SkDPoint testAtT = ptAtT(t);
71 cubicAtT = testAtT;
72 calcPos = (&cubicAtT.fX)[xAxis];
73 calcDist = calcPos - axisIntercept;
74 } while (!approximately_equal(calcPos, axisIntercept));
75 return t;
76}
77
Cary Clark7eb01e02016-12-08 14:36:32 -050078// get the rough scale of the cubic; used to determine if curvature is extreme
caryclark@google.com07393ca2013-04-08 11:47:37 +000079double SkDCubic::calcPrecision() const {
Cary Clark7eb01e02016-12-08 14:36:32 -050080 return ((fPts[1] - fPts[0]).length()
81 + (fPts[2] - fPts[1]).length()
82 + (fPts[3] - fPts[2]).length()) / gPrecisionUnit;
caryclark@google.com07393ca2013-04-08 11:47:37 +000083}
84
caryclark624637c2015-05-11 07:21:27 -070085/* classic one t subdivision */
86static void interp_cubic_coords(const double* src, double* dst, double t) {
87 double ab = SkDInterp(src[0], src[2], t);
88 double bc = SkDInterp(src[2], src[4], t);
89 double cd = SkDInterp(src[4], src[6], t);
90 double abc = SkDInterp(ab, bc, t);
91 double bcd = SkDInterp(bc, cd, t);
92 double abcd = SkDInterp(abc, bcd, t);
93
94 dst[0] = src[0];
95 dst[2] = ab;
96 dst[4] = abc;
97 dst[6] = abcd;
98 dst[8] = bcd;
99 dst[10] = cd;
100 dst[12] = src[6];
caryclark@google.com07393ca2013-04-08 11:47:37 +0000101}
102
caryclark624637c2015-05-11 07:21:27 -0700103SkDCubicPair SkDCubic::chopAt(double t) const {
104 SkDCubicPair dst;
105 if (t == 0.5) {
106 dst.pts[0] = fPts[0];
107 dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2;
108 dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2;
109 dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4;
110 dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4;
111 dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8;
112 dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8;
113 dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4;
114 dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4;
115 dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2;
116 dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2;
117 dst.pts[6] = fPts[3];
118 return dst;
119 }
120 interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t);
121 interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t);
122 return dst;
caryclark03b03ca2015-04-23 09:13:37 -0700123}
124
caryclark@google.com07393ca2013-04-08 11:47:37 +0000125void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) {
126 *A = src[6]; // d
127 *B = src[4] * 3; // 3*c
128 *C = src[2] * 3; // 3*b
129 *D = src[0]; // a
130 *A -= *D - *C + *B; // A = -a + 3*b - 3*c + d
131 *B += 3 * *D - 2 * *C; // B = 3*a - 6*b + 3*c
132 *C -= 3 * *D; // C = -3*a + 3*b
133}
134
caryclark@google.com07393ca2013-04-08 11:47:37 +0000135bool SkDCubic::endsAreExtremaInXOrY() const {
136 return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
137 && between(fPts[0].fX, fPts[2].fX, fPts[3].fX))
138 || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
139 && between(fPts[0].fY, fPts[2].fY, fPts[3].fY));
140}
141
caryclark54359292015-03-26 07:52:43 -0700142// Do a quick reject by rotating all points relative to a line formed by
143// a pair of one cubic's points. If the 2nd cubic's points
144// are on the line or on the opposite side from the 1st cubic's 'odd man', the
145// curves at most intersect at the endpoints.
146/* if returning true, check contains true if cubic's hull collapsed, making the cubic linear
147 if returning false, check contains true if the the cubic pair have only the end point in common
148*/
caryclark1049f122015-04-20 08:31:59 -0700149bool SkDCubic::hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const {
caryclark54359292015-03-26 07:52:43 -0700150 bool linear = true;
151 char hullOrder[4];
152 int hullCount = convexHull(hullOrder);
153 int end1 = hullOrder[0];
154 int hullIndex = 0;
155 const SkDPoint* endPt[2];
156 endPt[0] = &fPts[end1];
157 do {
158 hullIndex = (hullIndex + 1) % hullCount;
159 int end2 = hullOrder[hullIndex];
160 endPt[1] = &fPts[end2];
161 double origX = endPt[0]->fX;
162 double origY = endPt[0]->fY;
163 double adj = endPt[1]->fX - origX;
164 double opp = endPt[1]->fY - origY;
165 int oddManMask = other_two(end1, end2);
166 int oddMan = end1 ^ oddManMask;
167 double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp;
168 int oddMan2 = end2 ^ oddManMask;
169 double sign2 = (fPts[oddMan2].fY - origY) * adj - (fPts[oddMan2].fX - origX) * opp;
170 if (sign * sign2 < 0) {
171 continue;
172 }
173 if (approximately_zero(sign)) {
174 sign = sign2;
175 if (approximately_zero(sign)) {
176 continue;
177 }
178 }
179 linear = false;
180 bool foundOutlier = false;
caryclark1049f122015-04-20 08:31:59 -0700181 for (int n = 0; n < ptCount; ++n) {
182 double test = (pts[n].fY - origY) * adj - (pts[n].fX - origX) * opp;
caryclark54359292015-03-26 07:52:43 -0700183 if (test * sign > 0 && !precisely_zero(test)) {
184 foundOutlier = true;
185 break;
186 }
187 }
188 if (!foundOutlier) {
189 return false;
190 }
191 endPt[0] = endPt[1];
192 end1 = end2;
193 } while (hullIndex);
194 *isLinear = linear;
195 return true;
196}
197
caryclark1049f122015-04-20 08:31:59 -0700198bool SkDCubic::hullIntersects(const SkDCubic& c2, bool* isLinear) const {
199 return hullIntersects(c2.fPts, c2.kPointCount, isLinear);
200}
201
202bool SkDCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const {
203 return hullIntersects(quad.fPts, quad.kPointCount, isLinear);
204}
205
206bool SkDCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const {
207
208 return hullIntersects(conic.fPts, isLinear);
209}
210
caryclark@google.com07393ca2013-04-08 11:47:37 +0000211bool SkDCubic::isLinear(int startIndex, int endIndex) const {
caryclarke3a4e992016-09-28 09:22:17 -0700212 if (fPts[0].approximatelyDEqual(fPts[3])) {
213 return ((const SkDQuad *) this)->isLinear(0, 2);
214 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000215 SkLineParameters lineParameters;
216 lineParameters.cubicEndPoints(*this, startIndex, endIndex);
217 // FIXME: maybe it's possible to avoid this and compare non-normalized
218 lineParameters.normalize();
caryclark54359292015-03-26 07:52:43 -0700219 double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY),
220 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
221 double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY),
222 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
223 largest = SkTMax(largest, -tiniest);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000224 double distance = lineParameters.controlPtDistance(*this, 1);
caryclark54359292015-03-26 07:52:43 -0700225 if (!approximately_zero_when_compared_to(distance, largest)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000226 return false;
227 }
228 distance = lineParameters.controlPtDistance(*this, 2);
caryclark54359292015-03-26 07:52:43 -0700229 return approximately_zero_when_compared_to(distance, largest);
230}
231
Cary Clark7eb01e02016-12-08 14:36:32 -0500232// from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf
233// c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3
234// c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2
235// = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2
236static double derivative_at_t(const double* src, double t) {
237 double one_t = 1 - t;
238 double a = src[0];
239 double b = src[2];
240 double c = src[4];
241 double d = src[6];
242 return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
243}
244
245int SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) {
246 SkDCubic cubic;
247 cubic.set(pointsPtr);
248 if (cubic.monotonicInX() && cubic.monotonicInY()) {
249 return 0;
250 }
Chris Dalton43436542017-04-13 14:26:00 -0600251 SkScalar d[4];
caryclark54359292015-03-26 07:52:43 -0700252 SkCubicType cubicType = SkClassifyCubic(pointsPtr, d);
Cary Clark7eb01e02016-12-08 14:36:32 -0500253 switch (cubicType) {
Chris Dalton43436542017-04-13 14:26:00 -0600254 case SkCubicType::kLoop: {
Cary Clark7eb01e02016-12-08 14:36:32 -0500255 // crib code from gpu path utils that finds t values where loop self-intersects
256 // use it to find mid of t values which should be a friendly place to chop
Chris Dalton43436542017-04-13 14:26:00 -0600257 SkASSERT(d[0] < 0);
258 SkScalar tempSqrt = SkScalarSqrt(-d[0]);
259 SkScalar ls = d[2] - tempSqrt;
260 SkScalar lt = 2.f * d[1];
261 SkScalar ms = d[2] + tempSqrt;
262 SkScalar mt = 2.f * d[1];
Cary Clark7eb01e02016-12-08 14:36:32 -0500263 if (roughly_between(0, ls, lt) && roughly_between(0, ms, mt)) {
264 ls = ls / lt;
265 ms = ms / mt;
266 SkASSERT(roughly_between(0, ls, 1) && roughly_between(0, ms, 1));
267 t[0] = (ls + ms) / 2;
268 SkASSERT(roughly_between(0, *t, 1));
269 return (int) (t[0] > 0 && t[0] < 1);
270 }
caryclark54359292015-03-26 07:52:43 -0700271 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500272 // fall through if no t value found
Chris Dalton43436542017-04-13 14:26:00 -0600273 case SkCubicType::kSerpentine:
274 case SkCubicType::kLocalCusp:
275 case SkCubicType::kInfiniteCusp: {
Cary Clark7eb01e02016-12-08 14:36:32 -0500276 double inflectionTs[2];
277 int infTCount = cubic.findInflections(inflectionTs);
caryclark54359292015-03-26 07:52:43 -0700278 double maxCurvature[3];
279 int roots = cubic.findMaxCurvature(maxCurvature);
Cary Clark7eb01e02016-12-08 14:36:32 -0500280 #if DEBUG_CUBIC_SPLIT
caryclark03b03ca2015-04-23 09:13:37 -0700281 SkDebugf("%s\n", __FUNCTION__);
282 cubic.dump();
283 for (int index = 0; index < infTCount; ++index) {
284 SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]);
285 SkDPoint pt = cubic.ptAtT(inflectionTs[index]);
286 SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]);
287 SkDLine perp = {{pt - dPt, pt + dPt}};
288 perp.dump();
289 }
290 for (int index = 0; index < roots; ++index) {
291 SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]);
292 SkDPoint pt = cubic.ptAtT(maxCurvature[index]);
293 SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]);
294 SkDLine perp = {{pt - dPt, pt + dPt}};
295 perp.dump();
296 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500297 #endif
298 if (infTCount == 2) {
299 for (int index = 0; index < roots; ++index) {
300 if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) {
301 t[0] = maxCurvature[index];
302 return (int) (t[0] > 0 && t[0] < 1);
303 }
caryclark54359292015-03-26 07:52:43 -0700304 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500305 } else {
306 int resultCount = 0;
307 // FIXME: constant found through experimentation -- maybe there's a better way....
308 double precision = cubic.calcPrecision() * 2;
309 for (int index = 0; index < roots; ++index) {
310 double testT = maxCurvature[index];
311 if (0 >= testT || testT >= 1) {
312 continue;
313 }
314 // don't call dxdyAtT since we want (0,0) results
315 SkDVector dPt = { derivative_at_t(&cubic.fPts[0].fX, testT),
316 derivative_at_t(&cubic.fPts[0].fY, testT) };
317 double dPtLen = dPt.length();
318 if (dPtLen < precision) {
319 t[resultCount++] = testT;
320 }
321 }
322 if (!resultCount && infTCount == 1) {
323 t[0] = inflectionTs[0];
324 resultCount = (int) (t[0] > 0 && t[0] < 1);
325 }
326 return resultCount;
caryclark54359292015-03-26 07:52:43 -0700327 }
caryclark54359292015-03-26 07:52:43 -0700328 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500329 default:
330 ;
caryclark54359292015-03-26 07:52:43 -0700331 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500332 return 0;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000333}
334
caryclarkaec25102015-04-29 08:28:30 -0700335bool SkDCubic::monotonicInX() const {
336 return precisely_between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
337 && precisely_between(fPts[0].fX, fPts[2].fX, fPts[3].fX);
338}
339
caryclark@google.com07393ca2013-04-08 11:47:37 +0000340bool SkDCubic::monotonicInY() const {
caryclarkaec25102015-04-29 08:28:30 -0700341 return precisely_between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
342 && precisely_between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000343}
344
caryclark54359292015-03-26 07:52:43 -0700345void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const {
346 int offset = (int) !SkToBool(index);
347 o1Pts[0] = &fPts[offset];
348 o1Pts[1] = &fPts[++offset];
349 o1Pts[2] = &fPts[++offset];
350}
351
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000352int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept,
353 SearchAxis xAxis, double* validRoots) const {
354 extrema += findInflections(&extremeTs[extrema]);
355 extremeTs[extrema++] = 0;
356 extremeTs[extrema] = 1;
caryclark8a8accb2016-07-22 10:56:26 -0700357 SkASSERT(extrema < 6);
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000358 SkTQSort(extremeTs, extremeTs + extrema);
359 int validCount = 0;
360 for (int index = 0; index < extrema; ) {
361 double min = extremeTs[index];
362 double max = extremeTs[++index];
363 if (min == max) {
364 continue;
365 }
366 double newT = binarySearch(min, max, axisIntercept, xAxis);
367 if (newT >= 0) {
caryclark8a8accb2016-07-22 10:56:26 -0700368 if (validCount >= 3) {
369 return 0;
370 }
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000371 validRoots[validCount++] = newT;
372 }
373 }
374 return validCount;
375}
376
caryclark@google.com07393ca2013-04-08 11:47:37 +0000377// cubic roots
378
379static const double PI = 3.141592653589793;
380
381// from SkGeometry.cpp (and Numeric Solutions, 5.6)
382int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) {
383 double s[3];
384 int realRoots = RootsReal(A, B, C, D, s);
385 int foundRoots = SkDQuad::AddValidTs(s, realRoots, t);
caryclarkaec25102015-04-29 08:28:30 -0700386 for (int index = 0; index < realRoots; ++index) {
387 double tValue = s[index];
388 if (!approximately_one_or_less(tValue) && between(1, tValue, 1.00005)) {
389 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
390 if (approximately_equal(t[idx2], 1)) {
391 goto nextRoot;
392 }
393 }
394 SkASSERT(foundRoots < 3);
395 t[foundRoots++] = 1;
396 } else if (!approximately_zero_or_more(tValue) && between(-0.00005, tValue, 0)) {
397 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
398 if (approximately_equal(t[idx2], 0)) {
399 goto nextRoot;
400 }
401 }
402 SkASSERT(foundRoots < 3);
403 t[foundRoots++] = 0;
404 }
405nextRoot:
406 ;
407 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000408 return foundRoots;
409}
410
411int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
412#ifdef SK_DEBUG
413 // create a string mathematica understands
414 // GDB set print repe 15 # if repeated digits is a bother
415 // set print elements 400 # if line doesn't fit
416 char str[1024];
417 sk_bzero(str, sizeof(str));
418 SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
419 A, B, C, D);
caryclark@google.com570863f2013-09-16 15:55:01 +0000420 SkPathOpsDebug::MathematicaIze(str, sizeof(str));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000421#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
422 SkDebugf("%s\n", str);
423#endif
424#endif
425 if (approximately_zero(A)
426 && approximately_zero_when_compared_to(A, B)
427 && approximately_zero_when_compared_to(A, C)
428 && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic
429 return SkDQuad::RootsReal(B, C, D, s);
430 }
431 if (approximately_zero_when_compared_to(D, A)
432 && approximately_zero_when_compared_to(D, B)
433 && approximately_zero_when_compared_to(D, C)) { // 0 is one root
434 int num = SkDQuad::RootsReal(A, B, C, s);
435 for (int i = 0; i < num; ++i) {
436 if (approximately_zero(s[i])) {
437 return num;
438 }
439 }
440 s[num++] = 0;
441 return num;
442 }
443 if (approximately_zero(A + B + C + D)) { // 1 is one root
444 int num = SkDQuad::RootsReal(A, A + B, -D, s);
445 for (int i = 0; i < num; ++i) {
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000446 if (AlmostDequalUlps(s[i], 1)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000447 return num;
448 }
449 }
450 s[num++] = 1;
451 return num;
452 }
453 double a, b, c;
454 {
455 double invA = 1 / A;
456 a = B * invA;
457 b = C * invA;
458 c = D * invA;
459 }
460 double a2 = a * a;
461 double Q = (a2 - b * 3) / 9;
462 double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
463 double R2 = R * R;
464 double Q3 = Q * Q * Q;
465 double R2MinusQ3 = R2 - Q3;
466 double adiv3 = a / 3;
467 double r;
468 double* roots = s;
469 if (R2MinusQ3 < 0) { // we have 3 real roots
caryclark93ca8842016-05-27 05:24:37 -0700470 // the divide/root can, due to finite precisions, be slightly outside of -1...1
471 double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000472 double neg2RootQ = -2 * sqrt(Q);
473
474 r = neg2RootQ * cos(theta / 3) - adiv3;
475 *roots++ = r;
476
477 r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000478 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000479 *roots++ = r;
480 }
481 r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000482 if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000483 *roots++ = r;
484 }
485 } else { // we have 1 real root
486 double sqrtR2MinusQ3 = sqrt(R2MinusQ3);
487 double A = fabs(R) + sqrtR2MinusQ3;
488 A = SkDCubeRoot(A);
489 if (R > 0) {
490 A = -A;
491 }
492 if (A != 0) {
493 A += Q / A;
494 }
495 r = A - adiv3;
496 *roots++ = r;
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000497 if (AlmostDequalUlps((double) R2, (double) Q3)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000498 r = -A / 2 - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000499 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000500 *roots++ = r;
501 }
502 }
503 }
504 return static_cast<int>(roots - s);
505}
506
caryclark@google.com07393ca2013-04-08 11:47:37 +0000507// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
508SkDVector SkDCubic::dxdyAtT(double t) const {
509 SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
caryclark94c902e2015-08-18 07:12:43 -0700510 if (result.fX == 0 && result.fY == 0) {
511 if (t == 0) {
512 result = fPts[2] - fPts[0];
513 } else if (t == 1) {
514 result = fPts[3] - fPts[1];
515 } else {
516 // incomplete
517 SkDebugf("!c");
518 }
519 if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) {
520 result = fPts[3] - fPts[0];
521 }
522 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000523 return result;
524}
525
526// OPTIMIZE? share code with formulate_F1DotF2
527int SkDCubic::findInflections(double tValues[]) const {
528 double Ax = fPts[1].fX - fPts[0].fX;
529 double Ay = fPts[1].fY - fPts[0].fY;
530 double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX;
531 double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY;
532 double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX;
533 double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY;
534 return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues);
535}
536
537static void formulate_F1DotF2(const double src[], double coeff[4]) {
538 double a = src[2] - src[0];
539 double b = src[4] - 2 * src[2] + src[0];
540 double c = src[6] + 3 * (src[2] - src[4]) - src[0];
541 coeff[0] = c * c;
542 coeff[1] = 3 * b * c;
543 coeff[2] = 2 * b * b + c * a;
544 coeff[3] = a * b;
545}
546
547/** SkDCubic'(t) = At^2 + Bt + C, where
548 A = 3(-a + 3(b - c) + d)
549 B = 6(a - 2b + c)
550 C = 3(b - a)
551 Solve for t, keeping only those that fit between 0 < t < 1
552*/
caryclarkaec25102015-04-29 08:28:30 -0700553int SkDCubic::FindExtrema(const double src[], double tValues[2]) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000554 // we divide A,B,C by 3 to simplify
caryclarkaec25102015-04-29 08:28:30 -0700555 double a = src[0];
556 double b = src[2];
557 double c = src[4];
558 double d = src[6];
559 double A = d - a + 3 * (b - c);
560 double B = 2 * (a - b - b + c);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000561 double C = b - a;
562
563 return SkDQuad::RootsValidT(A, B, C, tValues);
564}
565
566/* from SkGeometry.cpp
567 Looking for F' dot F'' == 0
568
569 A = b - a
570 B = c - 2b + a
571 C = d - 3c + 3b - a
572
573 F' = 3Ct^2 + 6Bt + 3A
574 F'' = 6Ct + 6B
575
576 F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
577*/
578int SkDCubic::findMaxCurvature(double tValues[]) const {
579 double coeffX[4], coeffY[4];
580 int i;
581 formulate_F1DotF2(&fPts[0].fX, coeffX);
582 formulate_F1DotF2(&fPts[0].fY, coeffY);
583 for (i = 0; i < 4; i++) {
584 coeffX[i] = coeffX[i] + coeffY[i];
585 }
586 return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues);
587}
588
caryclark@google.com4fdbb222013-07-23 15:27:41 +0000589SkDPoint SkDCubic::ptAtT(double t) const {
590 if (0 == t) {
591 return fPts[0];
592 }
593 if (1 == t) {
594 return fPts[3];
595 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000596 double one_t = 1 - t;
597 double one_t2 = one_t * one_t;
598 double a = one_t2 * one_t;
599 double b = 3 * one_t2 * t;
600 double t2 = t * t;
601 double c = 3 * one_t * t2;
602 double d = t2 * t;
603 SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX,
604 a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY};
605 return result;
606}
607
608/*
609 Given a cubic c, t1, and t2, find a small cubic segment.
610
611 The new cubic is defined as points A, B, C, and D, where
612 s1 = 1 - t1
613 s2 = 1 - t2
614 A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1
615 D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2
616
617 We don't have B or C. So We define two equations to isolate them.
618 First, compute two reference T values 1/3 and 2/3 from t1 to t2:
619
620 c(at (2*t1 + t2)/3) == E
621 c(at (t1 + 2*t2)/3) == F
622
623 Next, compute where those values must be if we know the values of B and C:
624
625 _12 = A*2/3 + B*1/3
626 12_ = A*1/3 + B*2/3
627 _23 = B*2/3 + C*1/3
628 23_ = B*1/3 + C*2/3
629 _34 = C*2/3 + D*1/3
630 34_ = C*1/3 + D*2/3
631 _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
632 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
633 _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
634 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
635 _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3
636 = A*8/27 + B*12/27 + C*6/27 + D*1/27
637 = E
638 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3
639 = A*1/27 + B*6/27 + C*12/27 + D*8/27
640 = F
641 E*27 = A*8 + B*12 + C*6 + D
642 F*27 = A + B*6 + C*12 + D*8
643
644Group the known values on one side:
645
646 M = E*27 - A*8 - D = B*12 + C* 6
647 N = F*27 - A - D*8 = B* 6 + C*12
648 M*2 - N = B*18
649 N*2 - M = C*18
650 B = (M*2 - N)/18
651 C = (N*2 - M)/18
652 */
653
654static double interp_cubic_coords(const double* src, double t) {
655 double ab = SkDInterp(src[0], src[2], t);
656 double bc = SkDInterp(src[2], src[4], t);
657 double cd = SkDInterp(src[4], src[6], t);
658 double abc = SkDInterp(ab, bc, t);
659 double bcd = SkDInterp(bc, cd, t);
660 double abcd = SkDInterp(abc, bcd, t);
661 return abcd;
662}
663
664SkDCubic SkDCubic::subDivide(double t1, double t2) const {
caryclark@google.comd892bd82013-06-17 14:10:36 +0000665 if (t1 == 0 || t2 == 1) {
666 if (t1 == 0 && t2 == 1) {
667 return *this;
668 }
669 SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1);
670 SkDCubic dst = t1 == 0 ? pair.first() : pair.second();
671 return dst;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000672 }
673 SkDCubic dst;
674 double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1);
675 double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1);
676 double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3);
677 double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3);
678 double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3);
679 double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3);
680 double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2);
681 double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2);
682 double mx = ex * 27 - ax * 8 - dx;
683 double my = ey * 27 - ay * 8 - dy;
684 double nx = fx * 27 - ax - dx * 8;
685 double ny = fy * 27 - ay - dy * 8;
686 /* bx = */ dst[1].fX = (mx * 2 - nx) / 18;
687 /* by = */ dst[1].fY = (my * 2 - ny) / 18;
688 /* cx = */ dst[2].fX = (nx * 2 - mx) / 18;
689 /* cy = */ dst[2].fY = (ny * 2 - my) / 18;
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000690 // FIXME: call align() ?
caryclark@google.com07393ca2013-04-08 11:47:37 +0000691 return dst;
692}
693
694void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
695 double t1, double t2, SkDPoint dst[2]) const {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000696 SkASSERT(t1 != t2);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000697 // this approach assumes that the control points computed directly are accurate enough
698 SkDCubic sub = subDivide(t1, t2);
699 dst[0] = sub[1] + (a - sub[0]);
700 dst[1] = sub[2] + (d - sub[3]);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000701 if (t1 == 0 || t2 == 0) {
702 align(0, 1, t1 == 0 ? &dst[0] : &dst[1]);
703 }
704 if (t1 == 1 || t2 == 1) {
705 align(3, 2, t1 == 1 ? &dst[0] : &dst[1]);
706 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000707 if (AlmostBequalUlps(dst[0].fX, a.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000708 dst[0].fX = a.fX;
709 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000710 if (AlmostBequalUlps(dst[0].fY, a.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000711 dst[0].fY = a.fY;
712 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000713 if (AlmostBequalUlps(dst[1].fX, d.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000714 dst[1].fX = d.fX;
715 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000716 if (AlmostBequalUlps(dst[1].fY, d.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000717 dst[1].fY = d.fY;
718 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000719}
720
Cary Clark7eb01e02016-12-08 14:36:32 -0500721bool SkDCubic::toFloatPoints(SkPoint* pts) const {
722 const double* dCubic = &fPts[0].fX;
723 SkScalar* cubic = &pts[0].fX;
724 for (int index = 0; index < kPointCount * 2; ++index) {
725 *cubic++ = SkDoubleToScalar(*dCubic++);
726 }
727 return SkScalarsAreFinite(&pts->fX, kPointCount * 2);
728}
729
caryclark624637c2015-05-11 07:21:27 -0700730double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const {
731 double extremeTs[2];
732 double topT = -1;
733 int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs);
734 for (int index = 0; index < roots; ++index) {
735 double t = startT + (endT - startT) * extremeTs[index];
736 SkDPoint mid = dCurve.ptAtT(t);
737 if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) {
738 topT = t;
739 *topPt = mid;
740 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000741 }
caryclark624637c2015-05-11 07:21:27 -0700742 return topT;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000743}