blob: d842e2cc0c7fb841948a6f4a60a1c2fe8a49eae1 [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 }
caryclark54359292015-03-26 07:52:43 -0700251 SkScalar d[3];
252 SkCubicType cubicType = SkClassifyCubic(pointsPtr, d);
Cary Clark7eb01e02016-12-08 14:36:32 -0500253 switch (cubicType) {
254 case kLoop_SkCubicType: {
255 // 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
257 SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]);
258 SkScalar ls = d[1] - tempSqrt;
259 SkScalar lt = 2.f * d[0];
260 SkScalar ms = d[1] + tempSqrt;
261 SkScalar mt = 2.f * d[0];
262 if (roughly_between(0, ls, lt) && roughly_between(0, ms, mt)) {
263 ls = ls / lt;
264 ms = ms / mt;
265 SkASSERT(roughly_between(0, ls, 1) && roughly_between(0, ms, 1));
266 t[0] = (ls + ms) / 2;
267 SkASSERT(roughly_between(0, *t, 1));
268 return (int) (t[0] > 0 && t[0] < 1);
269 }
caryclark54359292015-03-26 07:52:43 -0700270 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500271 // fall through if no t value found
272 case kSerpentine_SkCubicType:
273 case kCusp_SkCubicType: {
274 double inflectionTs[2];
275 int infTCount = cubic.findInflections(inflectionTs);
caryclark54359292015-03-26 07:52:43 -0700276 double maxCurvature[3];
277 int roots = cubic.findMaxCurvature(maxCurvature);
Cary Clark7eb01e02016-12-08 14:36:32 -0500278 #if DEBUG_CUBIC_SPLIT
caryclark03b03ca2015-04-23 09:13:37 -0700279 SkDebugf("%s\n", __FUNCTION__);
280 cubic.dump();
281 for (int index = 0; index < infTCount; ++index) {
282 SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]);
283 SkDPoint pt = cubic.ptAtT(inflectionTs[index]);
284 SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]);
285 SkDLine perp = {{pt - dPt, pt + dPt}};
286 perp.dump();
287 }
288 for (int index = 0; index < roots; ++index) {
289 SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]);
290 SkDPoint pt = cubic.ptAtT(maxCurvature[index]);
291 SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]);
292 SkDLine perp = {{pt - dPt, pt + dPt}};
293 perp.dump();
294 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500295 #endif
296 if (infTCount == 2) {
297 for (int index = 0; index < roots; ++index) {
298 if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) {
299 t[0] = maxCurvature[index];
300 return (int) (t[0] > 0 && t[0] < 1);
301 }
caryclark54359292015-03-26 07:52:43 -0700302 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500303 } else {
304 int resultCount = 0;
305 // FIXME: constant found through experimentation -- maybe there's a better way....
306 double precision = cubic.calcPrecision() * 2;
307 for (int index = 0; index < roots; ++index) {
308 double testT = maxCurvature[index];
309 if (0 >= testT || testT >= 1) {
310 continue;
311 }
312 // don't call dxdyAtT since we want (0,0) results
313 SkDVector dPt = { derivative_at_t(&cubic.fPts[0].fX, testT),
314 derivative_at_t(&cubic.fPts[0].fY, testT) };
315 double dPtLen = dPt.length();
316 if (dPtLen < precision) {
317 t[resultCount++] = testT;
318 }
319 }
320 if (!resultCount && infTCount == 1) {
321 t[0] = inflectionTs[0];
322 resultCount = (int) (t[0] > 0 && t[0] < 1);
323 }
324 return resultCount;
caryclark54359292015-03-26 07:52:43 -0700325 }
caryclark54359292015-03-26 07:52:43 -0700326 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500327 default:
328 ;
caryclark54359292015-03-26 07:52:43 -0700329 }
Cary Clark7eb01e02016-12-08 14:36:32 -0500330 return 0;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000331}
332
caryclarkaec25102015-04-29 08:28:30 -0700333bool SkDCubic::monotonicInX() const {
334 return precisely_between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
335 && precisely_between(fPts[0].fX, fPts[2].fX, fPts[3].fX);
336}
337
caryclark@google.com07393ca2013-04-08 11:47:37 +0000338bool SkDCubic::monotonicInY() const {
caryclarkaec25102015-04-29 08:28:30 -0700339 return precisely_between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
340 && precisely_between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000341}
342
caryclark54359292015-03-26 07:52:43 -0700343void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const {
344 int offset = (int) !SkToBool(index);
345 o1Pts[0] = &fPts[offset];
346 o1Pts[1] = &fPts[++offset];
347 o1Pts[2] = &fPts[++offset];
348}
349
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000350int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept,
351 SearchAxis xAxis, double* validRoots) const {
352 extrema += findInflections(&extremeTs[extrema]);
353 extremeTs[extrema++] = 0;
354 extremeTs[extrema] = 1;
caryclark8a8accb2016-07-22 10:56:26 -0700355 SkASSERT(extrema < 6);
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000356 SkTQSort(extremeTs, extremeTs + extrema);
357 int validCount = 0;
358 for (int index = 0; index < extrema; ) {
359 double min = extremeTs[index];
360 double max = extremeTs[++index];
361 if (min == max) {
362 continue;
363 }
364 double newT = binarySearch(min, max, axisIntercept, xAxis);
365 if (newT >= 0) {
caryclark8a8accb2016-07-22 10:56:26 -0700366 if (validCount >= 3) {
367 return 0;
368 }
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000369 validRoots[validCount++] = newT;
370 }
371 }
372 return validCount;
373}
374
caryclark@google.com07393ca2013-04-08 11:47:37 +0000375// cubic roots
376
377static const double PI = 3.141592653589793;
378
379// from SkGeometry.cpp (and Numeric Solutions, 5.6)
380int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) {
381 double s[3];
382 int realRoots = RootsReal(A, B, C, D, s);
383 int foundRoots = SkDQuad::AddValidTs(s, realRoots, t);
caryclarkaec25102015-04-29 08:28:30 -0700384 for (int index = 0; index < realRoots; ++index) {
385 double tValue = s[index];
386 if (!approximately_one_or_less(tValue) && between(1, tValue, 1.00005)) {
387 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
388 if (approximately_equal(t[idx2], 1)) {
389 goto nextRoot;
390 }
391 }
392 SkASSERT(foundRoots < 3);
393 t[foundRoots++] = 1;
394 } else if (!approximately_zero_or_more(tValue) && between(-0.00005, tValue, 0)) {
395 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
396 if (approximately_equal(t[idx2], 0)) {
397 goto nextRoot;
398 }
399 }
400 SkASSERT(foundRoots < 3);
401 t[foundRoots++] = 0;
402 }
403nextRoot:
404 ;
405 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000406 return foundRoots;
407}
408
409int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
410#ifdef SK_DEBUG
411 // create a string mathematica understands
412 // GDB set print repe 15 # if repeated digits is a bother
413 // set print elements 400 # if line doesn't fit
414 char str[1024];
415 sk_bzero(str, sizeof(str));
416 SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
417 A, B, C, D);
caryclark@google.com570863f2013-09-16 15:55:01 +0000418 SkPathOpsDebug::MathematicaIze(str, sizeof(str));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000419#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
420 SkDebugf("%s\n", str);
421#endif
422#endif
423 if (approximately_zero(A)
424 && approximately_zero_when_compared_to(A, B)
425 && approximately_zero_when_compared_to(A, C)
426 && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic
427 return SkDQuad::RootsReal(B, C, D, s);
428 }
429 if (approximately_zero_when_compared_to(D, A)
430 && approximately_zero_when_compared_to(D, B)
431 && approximately_zero_when_compared_to(D, C)) { // 0 is one root
432 int num = SkDQuad::RootsReal(A, B, C, s);
433 for (int i = 0; i < num; ++i) {
434 if (approximately_zero(s[i])) {
435 return num;
436 }
437 }
438 s[num++] = 0;
439 return num;
440 }
441 if (approximately_zero(A + B + C + D)) { // 1 is one root
442 int num = SkDQuad::RootsReal(A, A + B, -D, s);
443 for (int i = 0; i < num; ++i) {
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000444 if (AlmostDequalUlps(s[i], 1)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000445 return num;
446 }
447 }
448 s[num++] = 1;
449 return num;
450 }
451 double a, b, c;
452 {
453 double invA = 1 / A;
454 a = B * invA;
455 b = C * invA;
456 c = D * invA;
457 }
458 double a2 = a * a;
459 double Q = (a2 - b * 3) / 9;
460 double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
461 double R2 = R * R;
462 double Q3 = Q * Q * Q;
463 double R2MinusQ3 = R2 - Q3;
464 double adiv3 = a / 3;
465 double r;
466 double* roots = s;
467 if (R2MinusQ3 < 0) { // we have 3 real roots
caryclark93ca8842016-05-27 05:24:37 -0700468 // the divide/root can, due to finite precisions, be slightly outside of -1...1
469 double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000470 double neg2RootQ = -2 * sqrt(Q);
471
472 r = neg2RootQ * cos(theta / 3) - adiv3;
473 *roots++ = r;
474
475 r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000476 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000477 *roots++ = r;
478 }
479 r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000480 if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000481 *roots++ = r;
482 }
483 } else { // we have 1 real root
484 double sqrtR2MinusQ3 = sqrt(R2MinusQ3);
485 double A = fabs(R) + sqrtR2MinusQ3;
486 A = SkDCubeRoot(A);
487 if (R > 0) {
488 A = -A;
489 }
490 if (A != 0) {
491 A += Q / A;
492 }
493 r = A - adiv3;
494 *roots++ = r;
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000495 if (AlmostDequalUlps((double) R2, (double) Q3)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000496 r = -A / 2 - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000497 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000498 *roots++ = r;
499 }
500 }
501 }
502 return static_cast<int>(roots - s);
503}
504
caryclark@google.com07393ca2013-04-08 11:47:37 +0000505// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
506SkDVector SkDCubic::dxdyAtT(double t) const {
507 SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
caryclark94c902e2015-08-18 07:12:43 -0700508 if (result.fX == 0 && result.fY == 0) {
509 if (t == 0) {
510 result = fPts[2] - fPts[0];
511 } else if (t == 1) {
512 result = fPts[3] - fPts[1];
513 } else {
514 // incomplete
515 SkDebugf("!c");
516 }
517 if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) {
518 result = fPts[3] - fPts[0];
519 }
520 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000521 return result;
522}
523
524// OPTIMIZE? share code with formulate_F1DotF2
525int SkDCubic::findInflections(double tValues[]) const {
526 double Ax = fPts[1].fX - fPts[0].fX;
527 double Ay = fPts[1].fY - fPts[0].fY;
528 double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX;
529 double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY;
530 double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX;
531 double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY;
532 return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues);
533}
534
535static void formulate_F1DotF2(const double src[], double coeff[4]) {
536 double a = src[2] - src[0];
537 double b = src[4] - 2 * src[2] + src[0];
538 double c = src[6] + 3 * (src[2] - src[4]) - src[0];
539 coeff[0] = c * c;
540 coeff[1] = 3 * b * c;
541 coeff[2] = 2 * b * b + c * a;
542 coeff[3] = a * b;
543}
544
545/** SkDCubic'(t) = At^2 + Bt + C, where
546 A = 3(-a + 3(b - c) + d)
547 B = 6(a - 2b + c)
548 C = 3(b - a)
549 Solve for t, keeping only those that fit between 0 < t < 1
550*/
caryclarkaec25102015-04-29 08:28:30 -0700551int SkDCubic::FindExtrema(const double src[], double tValues[2]) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000552 // we divide A,B,C by 3 to simplify
caryclarkaec25102015-04-29 08:28:30 -0700553 double a = src[0];
554 double b = src[2];
555 double c = src[4];
556 double d = src[6];
557 double A = d - a + 3 * (b - c);
558 double B = 2 * (a - b - b + c);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000559 double C = b - a;
560
561 return SkDQuad::RootsValidT(A, B, C, tValues);
562}
563
564/* from SkGeometry.cpp
565 Looking for F' dot F'' == 0
566
567 A = b - a
568 B = c - 2b + a
569 C = d - 3c + 3b - a
570
571 F' = 3Ct^2 + 6Bt + 3A
572 F'' = 6Ct + 6B
573
574 F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
575*/
576int SkDCubic::findMaxCurvature(double tValues[]) const {
577 double coeffX[4], coeffY[4];
578 int i;
579 formulate_F1DotF2(&fPts[0].fX, coeffX);
580 formulate_F1DotF2(&fPts[0].fY, coeffY);
581 for (i = 0; i < 4; i++) {
582 coeffX[i] = coeffX[i] + coeffY[i];
583 }
584 return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues);
585}
586
caryclark@google.com4fdbb222013-07-23 15:27:41 +0000587SkDPoint SkDCubic::ptAtT(double t) const {
588 if (0 == t) {
589 return fPts[0];
590 }
591 if (1 == t) {
592 return fPts[3];
593 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000594 double one_t = 1 - t;
595 double one_t2 = one_t * one_t;
596 double a = one_t2 * one_t;
597 double b = 3 * one_t2 * t;
598 double t2 = t * t;
599 double c = 3 * one_t * t2;
600 double d = t2 * t;
601 SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX,
602 a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY};
603 return result;
604}
605
606/*
607 Given a cubic c, t1, and t2, find a small cubic segment.
608
609 The new cubic is defined as points A, B, C, and D, where
610 s1 = 1 - t1
611 s2 = 1 - t2
612 A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1
613 D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2
614
615 We don't have B or C. So We define two equations to isolate them.
616 First, compute two reference T values 1/3 and 2/3 from t1 to t2:
617
618 c(at (2*t1 + t2)/3) == E
619 c(at (t1 + 2*t2)/3) == F
620
621 Next, compute where those values must be if we know the values of B and C:
622
623 _12 = A*2/3 + B*1/3
624 12_ = A*1/3 + B*2/3
625 _23 = B*2/3 + C*1/3
626 23_ = B*1/3 + C*2/3
627 _34 = C*2/3 + D*1/3
628 34_ = C*1/3 + D*2/3
629 _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
630 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
631 _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
632 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
633 _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3
634 = A*8/27 + B*12/27 + C*6/27 + D*1/27
635 = E
636 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3
637 = A*1/27 + B*6/27 + C*12/27 + D*8/27
638 = F
639 E*27 = A*8 + B*12 + C*6 + D
640 F*27 = A + B*6 + C*12 + D*8
641
642Group the known values on one side:
643
644 M = E*27 - A*8 - D = B*12 + C* 6
645 N = F*27 - A - D*8 = B* 6 + C*12
646 M*2 - N = B*18
647 N*2 - M = C*18
648 B = (M*2 - N)/18
649 C = (N*2 - M)/18
650 */
651
652static double interp_cubic_coords(const double* src, double t) {
653 double ab = SkDInterp(src[0], src[2], t);
654 double bc = SkDInterp(src[2], src[4], t);
655 double cd = SkDInterp(src[4], src[6], t);
656 double abc = SkDInterp(ab, bc, t);
657 double bcd = SkDInterp(bc, cd, t);
658 double abcd = SkDInterp(abc, bcd, t);
659 return abcd;
660}
661
662SkDCubic SkDCubic::subDivide(double t1, double t2) const {
caryclark@google.comd892bd82013-06-17 14:10:36 +0000663 if (t1 == 0 || t2 == 1) {
664 if (t1 == 0 && t2 == 1) {
665 return *this;
666 }
667 SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1);
668 SkDCubic dst = t1 == 0 ? pair.first() : pair.second();
669 return dst;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000670 }
671 SkDCubic dst;
672 double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1);
673 double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1);
674 double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3);
675 double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3);
676 double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3);
677 double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3);
678 double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2);
679 double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2);
680 double mx = ex * 27 - ax * 8 - dx;
681 double my = ey * 27 - ay * 8 - dy;
682 double nx = fx * 27 - ax - dx * 8;
683 double ny = fy * 27 - ay - dy * 8;
684 /* bx = */ dst[1].fX = (mx * 2 - nx) / 18;
685 /* by = */ dst[1].fY = (my * 2 - ny) / 18;
686 /* cx = */ dst[2].fX = (nx * 2 - mx) / 18;
687 /* cy = */ dst[2].fY = (ny * 2 - my) / 18;
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000688 // FIXME: call align() ?
caryclark@google.com07393ca2013-04-08 11:47:37 +0000689 return dst;
690}
691
692void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
693 double t1, double t2, SkDPoint dst[2]) const {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000694 SkASSERT(t1 != t2);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000695 // this approach assumes that the control points computed directly are accurate enough
696 SkDCubic sub = subDivide(t1, t2);
697 dst[0] = sub[1] + (a - sub[0]);
698 dst[1] = sub[2] + (d - sub[3]);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000699 if (t1 == 0 || t2 == 0) {
700 align(0, 1, t1 == 0 ? &dst[0] : &dst[1]);
701 }
702 if (t1 == 1 || t2 == 1) {
703 align(3, 2, t1 == 1 ? &dst[0] : &dst[1]);
704 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000705 if (AlmostBequalUlps(dst[0].fX, a.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000706 dst[0].fX = a.fX;
707 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000708 if (AlmostBequalUlps(dst[0].fY, a.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000709 dst[0].fY = a.fY;
710 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000711 if (AlmostBequalUlps(dst[1].fX, d.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000712 dst[1].fX = d.fX;
713 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000714 if (AlmostBequalUlps(dst[1].fY, d.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000715 dst[1].fY = d.fY;
716 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000717}
718
Cary Clark7eb01e02016-12-08 14:36:32 -0500719bool SkDCubic::toFloatPoints(SkPoint* pts) const {
720 const double* dCubic = &fPts[0].fX;
721 SkScalar* cubic = &pts[0].fX;
722 for (int index = 0; index < kPointCount * 2; ++index) {
723 *cubic++ = SkDoubleToScalar(*dCubic++);
724 }
725 return SkScalarsAreFinite(&pts->fX, kPointCount * 2);
726}
727
caryclark624637c2015-05-11 07:21:27 -0700728double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const {
729 double extremeTs[2];
730 double topT = -1;
731 int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs);
732 for (int index = 0; index < roots; ++index) {
733 double t = startT + (endT - startT) * extremeTs[index];
734 SkDPoint mid = dCurve.ptAtT(t);
735 if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) {
736 topT = t;
737 *topPt = mid;
738 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000739 }
caryclark624637c2015-05-11 07:21:27 -0700740 return topT;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000741}