xref: /aosp_15_r20/external/skia/src/pathops/SkPathOpsCubic.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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  */
7 #include "src/pathops/SkPathOpsCubic.h"
8 
9 #include "include/private/base/SkFloatingPoint.h"
10 #include "include/private/base/SkTPin.h"
11 #include "include/private/base/SkTo.h"
12 #include "src/base/SkTSort.h"
13 #include "src/core/SkGeometry.h"
14 #include "src/pathops/SkIntersections.h"
15 #include "src/pathops/SkLineParameters.h"
16 #include "src/pathops/SkPathOpsConic.h"
17 #include "src/pathops/SkPathOpsQuad.h"
18 #include "src/pathops/SkPathOpsRect.h"
19 #include "src/pathops/SkPathOpsTypes.h"
20 
21 #include <algorithm>
22 #include <cmath>
23 
24 struct SkDLine;
25 
26 const int SkDCubic::gPrecisionUnit = 256;  // FIXME: test different values in test framework
27 
align(int endIndex,int ctrlIndex,SkDPoint * dstPt) const28 void SkDCubic::align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const {
29     if (fPts[endIndex].fX == fPts[ctrlIndex].fX) {
30         dstPt->fX = fPts[endIndex].fX;
31     }
32     if (fPts[endIndex].fY == fPts[ctrlIndex].fY) {
33         dstPt->fY = fPts[endIndex].fY;
34     }
35 }
36 
37 // give up when changing t no longer moves point
38 // also, copy point rather than recompute it when it does change
binarySearch(double min,double max,double axisIntercept,SearchAxis xAxis) const39 double SkDCubic::binarySearch(double min, double max, double axisIntercept,
40         SearchAxis xAxis) const {
41     double t = (min + max) / 2;
42     double step = (t - min) / 2;
43     SkDPoint cubicAtT = ptAtT(t);
44     double calcPos = (&cubicAtT.fX)[xAxis];
45     double calcDist = calcPos - axisIntercept;
46     do {
47         double priorT = std::max(min, t - step);
48         SkDPoint lessPt = ptAtT(priorT);
49         if (approximately_equal_half(lessPt.fX, cubicAtT.fX)
50                 && approximately_equal_half(lessPt.fY, cubicAtT.fY)) {
51             return -1;  // binary search found no point at this axis intercept
52         }
53         double lessDist = (&lessPt.fX)[xAxis] - axisIntercept;
54 #if DEBUG_CUBIC_BINARY_SEARCH
55         SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist,
56                 step, lessDist);
57 #endif
58         double lastStep = step;
59         step /= 2;
60         if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) {
61             t = priorT;
62         } else {
63             double nextT = t + lastStep;
64             if (nextT > max) {
65                 return -1;
66             }
67             SkDPoint morePt = ptAtT(nextT);
68             if (approximately_equal_half(morePt.fX, cubicAtT.fX)
69                     && approximately_equal_half(morePt.fY, cubicAtT.fY)) {
70                 return -1;  // binary search found no point at this axis intercept
71             }
72             double moreDist = (&morePt.fX)[xAxis] - axisIntercept;
73             if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) {
74                 continue;
75             }
76             t = nextT;
77         }
78         SkDPoint testAtT = ptAtT(t);
79         cubicAtT = testAtT;
80         calcPos = (&cubicAtT.fX)[xAxis];
81         calcDist = calcPos - axisIntercept;
82     } while (!approximately_equal(calcPos, axisIntercept));
83     return t;
84 }
85 
86 // get the rough scale of the cubic; used to determine if curvature is extreme
calcPrecision() const87 double SkDCubic::calcPrecision() const {
88     return ((fPts[1] - fPts[0]).length()
89             + (fPts[2] - fPts[1]).length()
90             + (fPts[3] - fPts[2]).length()) / gPrecisionUnit;
91 }
92 
93 /* classic one t subdivision */
interp_cubic_coords(const double * src,double * dst,double t)94 static void interp_cubic_coords(const double* src, double* dst, double t) {
95     double ab = SkDInterp(src[0], src[2], t);
96     double bc = SkDInterp(src[2], src[4], t);
97     double cd = SkDInterp(src[4], src[6], t);
98     double abc = SkDInterp(ab, bc, t);
99     double bcd = SkDInterp(bc, cd, t);
100     double abcd = SkDInterp(abc, bcd, t);
101 
102     dst[0] = src[0];
103     dst[2] = ab;
104     dst[4] = abc;
105     dst[6] = abcd;
106     dst[8] = bcd;
107     dst[10] = cd;
108     dst[12] = src[6];
109 }
110 
chopAt(double t) const111 SkDCubicPair SkDCubic::chopAt(double t) const {
112     SkDCubicPair dst;
113     if (t == 0.5) {
114         dst.pts[0] = fPts[0];
115         dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2;
116         dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2;
117         dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4;
118         dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4;
119         dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8;
120         dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8;
121         dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4;
122         dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4;
123         dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2;
124         dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2;
125         dst.pts[6] = fPts[3];
126         return dst;
127     }
128     interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t);
129     interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t);
130     return dst;
131 }
132 
133 // TODO(skbug.com/14063) deduplicate this with SkBezierCubic::ConvertToPolynomial
Coefficients(const double * src,double * A,double * B,double * C,double * D)134 void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) {
135     *A = src[6];  // d
136     *B = src[4] * 3;  // 3*c
137     *C = src[2] * 3;  // 3*b
138     *D = src[0];  // a
139     *A -= *D - *C + *B;     // A =   -a + 3*b - 3*c + d
140     *B += 3 * *D - 2 * *C;  // B =  3*a - 6*b + 3*c
141     *C -= 3 * *D;           // C = -3*a + 3*b
142 }
143 
endsAreExtremaInXOrY() const144 bool SkDCubic::endsAreExtremaInXOrY() const {
145     return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
146             && between(fPts[0].fX, fPts[2].fX, fPts[3].fX))
147             || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
148             && between(fPts[0].fY, fPts[2].fY, fPts[3].fY));
149 }
150 
151 // Do a quick reject by rotating all points relative to a line formed by
152 // a pair of one cubic's points. If the 2nd cubic's points
153 // are on the line or on the opposite side from the 1st cubic's 'odd man', the
154 // curves at most intersect at the endpoints.
155 /* if returning true, check contains true if cubic's hull collapsed, making the cubic linear
156    if returning false, check contains true if the the cubic pair have only the end point in common
157 */
hullIntersects(const SkDPoint * pts,int ptCount,bool * isLinear) const158 bool SkDCubic::hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const {
159     bool linear = true;
160     char hullOrder[4];
161     int hullCount = convexHull(hullOrder);
162     int end1 = hullOrder[0];
163     int hullIndex = 0;
164     const SkDPoint* endPt[2];
165     endPt[0] = &fPts[end1];
166     do {
167         hullIndex = (hullIndex + 1) % hullCount;
168         int end2 = hullOrder[hullIndex];
169         endPt[1] = &fPts[end2];
170         double origX = endPt[0]->fX;
171         double origY = endPt[0]->fY;
172         double adj = endPt[1]->fX - origX;
173         double opp = endPt[1]->fY - origY;
174         int oddManMask = other_two(end1, end2);
175         int oddMan = end1 ^ oddManMask;
176         double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp;
177         int oddMan2 = end2 ^ oddManMask;
178         double sign2 = (fPts[oddMan2].fY - origY) * adj - (fPts[oddMan2].fX - origX) * opp;
179         if (sign * sign2 < 0) {
180             continue;
181         }
182         if (approximately_zero(sign)) {
183             sign = sign2;
184             if (approximately_zero(sign)) {
185                 continue;
186             }
187         }
188         linear = false;
189         bool foundOutlier = false;
190         for (int n = 0; n < ptCount; ++n) {
191             double test = (pts[n].fY - origY) * adj - (pts[n].fX - origX) * opp;
192             if (test * sign > 0 && !precisely_zero(test)) {
193                 foundOutlier = true;
194                 break;
195             }
196         }
197         if (!foundOutlier) {
198             return false;
199         }
200         endPt[0] = endPt[1];
201         end1 = end2;
202     } while (hullIndex);
203     *isLinear = linear;
204     return true;
205 }
206 
hullIntersects(const SkDCubic & c2,bool * isLinear) const207 bool SkDCubic::hullIntersects(const SkDCubic& c2, bool* isLinear) const {
208     return hullIntersects(c2.fPts, SkDCubic::kPointCount, isLinear);
209 }
210 
hullIntersects(const SkDQuad & quad,bool * isLinear) const211 bool SkDCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const {
212     return hullIntersects(quad.fPts, SkDQuad::kPointCount, isLinear);
213 }
214 
hullIntersects(const SkDConic & conic,bool * isLinear) const215 bool SkDCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const {
216 
217     return hullIntersects(conic.fPts, isLinear);
218 }
219 
isLinear(int startIndex,int endIndex) const220 bool SkDCubic::isLinear(int startIndex, int endIndex) const {
221     if (fPts[0].approximatelyDEqual(fPts[3]))  {
222         return ((const SkDQuad *) this)->isLinear(0, 2);
223     }
224     SkLineParameters lineParameters;
225     lineParameters.cubicEndPoints(*this, startIndex, endIndex);
226     // FIXME: maybe it's possible to avoid this and compare non-normalized
227     lineParameters.normalize();
228     double tiniest = std::min(std::min(std::min(std::min(std::min(std::min(std::min(fPts[0].fX, fPts[0].fY),
229             fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
230     double largest = std::max(std::max(std::max(std::max(std::max(std::max(std::max(fPts[0].fX, fPts[0].fY),
231             fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
232     largest = std::max(largest, -tiniest);
233     double distance = lineParameters.controlPtDistance(*this, 1);
234     if (!approximately_zero_when_compared_to(distance, largest)) {
235         return false;
236     }
237     distance = lineParameters.controlPtDistance(*this, 2);
238     return approximately_zero_when_compared_to(distance, largest);
239 }
240 
241 // from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf
242 // c(t)  = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3
243 // c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2
244 //       = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2
derivative_at_t(const double * src,double t)245 static double derivative_at_t(const double* src, double t) {
246     double one_t = 1 - t;
247     double a = src[0];
248     double b = src[2];
249     double c = src[4];
250     double d = src[6];
251     return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
252 }
253 
ComplexBreak(const SkPoint pointsPtr[4],SkScalar * t)254 int SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) {
255     SkDCubic cubic;
256     cubic.set(pointsPtr);
257     if (cubic.monotonicInX() && cubic.monotonicInY()) {
258         return 0;
259     }
260     double tt[2], ss[2];
261     SkCubicType cubicType = SkClassifyCubic(pointsPtr, tt, ss);
262     switch (cubicType) {
263         case SkCubicType::kLoop: {
264             const double &td = tt[0], &te = tt[1], &sd = ss[0], &se = ss[1];
265             if (roughly_between(0, td, sd) && roughly_between(0, te, se)) {
266                 t[0] = static_cast<SkScalar>((td * se + te * sd) / (2 * sd * se));
267                 return (int) (t[0] > 0 && t[0] < 1);
268             }
269         }
270         [[fallthrough]]; // fall through if no t value found
271         case SkCubicType::kSerpentine:
272         case SkCubicType::kLocalCusp:
273         case SkCubicType::kCuspAtInfinity: {
274             double inflectionTs[2];
275             int infTCount = cubic.findInflections(inflectionTs);
276             double maxCurvature[3];
277             int roots = cubic.findMaxCurvature(maxCurvature);
278     #if DEBUG_CUBIC_SPLIT
279             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             }
295     #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                     }
302                 }
303             } 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;
325             }
326             break;
327         }
328         default:
329             break;
330     }
331     return 0;
332 }
333 
monotonicInX() const334 bool SkDCubic::monotonicInX() const {
335     return precisely_between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
336             && precisely_between(fPts[0].fX, fPts[2].fX, fPts[3].fX);
337 }
338 
monotonicInY() const339 bool SkDCubic::monotonicInY() const {
340     return precisely_between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
341             && precisely_between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
342 }
343 
otherPts(int index,const SkDPoint * o1Pts[kPointCount-1]) const344 void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const {
345     int offset = (int) !SkToBool(index);
346     o1Pts[0] = &fPts[offset];
347     o1Pts[1] = &fPts[++offset];
348     o1Pts[2] = &fPts[++offset];
349 }
350 
searchRoots(double extremeTs[6],int extrema,double axisIntercept,SearchAxis xAxis,double * validRoots) const351 int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept,
352         SearchAxis xAxis, double* validRoots) const {
353     extrema += findInflections(&extremeTs[extrema]);
354     extremeTs[extrema++] = 0;
355     extremeTs[extrema] = 1;
356     SkASSERT(extrema < 6);
357     SkTQSort(extremeTs, extremeTs + extrema + 1);
358     int validCount = 0;
359     for (int index = 0; index < extrema; ) {
360         double min = extremeTs[index];
361         double max = extremeTs[++index];
362         if (min == max) {
363             continue;
364         }
365         double newT = binarySearch(min, max, axisIntercept, xAxis);
366         if (newT >= 0) {
367             if (validCount >= 3) {
368                 return 0;
369             }
370             validRoots[validCount++] = newT;
371         }
372     }
373     return validCount;
374 }
375 
376 // cubic roots
377 
378 static const double PI = 3.141592653589793;
379 
380 // from SkGeometry.cpp (and Numeric Solutions, 5.6)
381 // // TODO(skbug.com/14063) Deduplicate with SkCubics::RootsValidT
RootsValidT(double A,double B,double C,double D,double t[3])382 int 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);
386     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         }
405 nextRoot:
406         ;
407     }
408     return foundRoots;
409 }
410 
411 // TODO(skbug.com/14063) Deduplicate with SkCubics::RootsReal
RootsReal(double A,double B,double C,double D,double s[3])412 int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
413 #ifdef SK_DEBUG
414     #if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
415     // create a string mathematica understands
416     // GDB set print repe 15 # if repeated digits is a bother
417     //     set print elements 400 # if line doesn't fit
418     char str[1024];
419     sk_bzero(str, sizeof(str));
420     snprintf(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
421             A, B, C, D);
422     SkPathOpsDebug::MathematicaIze(str, sizeof(str));
423     SkDebugf("%s\n", str);
424     #endif
425 #endif
426     if (approximately_zero(A)
427             && approximately_zero_when_compared_to(A, B)
428             && approximately_zero_when_compared_to(A, C)
429             && approximately_zero_when_compared_to(A, D)) {  // we're just a quadratic
430         return SkDQuad::RootsReal(B, C, D, s);
431     }
432     if (approximately_zero_when_compared_to(D, A)
433             && approximately_zero_when_compared_to(D, B)
434             && approximately_zero_when_compared_to(D, C)) {  // 0 is one root
435         int num = SkDQuad::RootsReal(A, B, C, s);
436         for (int i = 0; i < num; ++i) {
437             if (approximately_zero(s[i])) {
438                 return num;
439             }
440         }
441         s[num++] = 0;
442         return num;
443     }
444     if (approximately_zero(A + B + C + D)) {  // 1 is one root
445         int num = SkDQuad::RootsReal(A, A + B, -D, s);
446         for (int i = 0; i < num; ++i) {
447             if (AlmostDequalUlps(s[i], 1)) {
448                 return num;
449             }
450         }
451         s[num++] = 1;
452         return num;
453     }
454     double a, b, c;
455     {
456         double invA = 1 / A;
457         a = B * invA;
458         b = C * invA;
459         c = D * invA;
460     }
461     double a2 = a * a;
462     double Q = (a2 - b * 3) / 9;
463     double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
464     double R2 = R * R;
465     double Q3 = Q * Q * Q;
466     double R2MinusQ3 = R2 - Q3;
467     double adiv3 = a / 3;
468     double r;
469     double* roots = s;
470     if (R2MinusQ3 < 0) {   // we have 3 real roots
471         // the divide/root can, due to finite precisions, be slightly outside of -1...1
472         double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.));
473         double neg2RootQ = -2 * sqrt(Q);
474 
475         r = neg2RootQ * cos(theta / 3) - adiv3;
476         *roots++ = r;
477 
478         r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
479         if (!AlmostDequalUlps(s[0], r)) {
480             *roots++ = r;
481         }
482         r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
483         if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) {
484             *roots++ = r;
485         }
486     } else {  // we have 1 real root
487         double sqrtR2MinusQ3 = sqrt(R2MinusQ3);
488         A = fabs(R) + sqrtR2MinusQ3;
489         A = std::cbrt(A); // cube root
490         if (R > 0) {
491             A = -A;
492         }
493         if (A != 0) {
494             A += Q / A;
495         }
496         r = A - adiv3;
497         *roots++ = r;
498         if (AlmostDequalUlps((double) R2, (double) Q3)) {
499             r = -A / 2 - adiv3;
500             if (!AlmostDequalUlps(s[0], r)) {
501                 *roots++ = r;
502             }
503         }
504     }
505     return static_cast<int>(roots - s);
506 }
507 
508 // OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
dxdyAtT(double t) const509 SkDVector SkDCubic::dxdyAtT(double t) const {
510     SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
511     if (result.fX == 0 && result.fY == 0) {
512         if (t == 0) {
513             result = fPts[2] - fPts[0];
514         } else if (t == 1) {
515             result = fPts[3] - fPts[1];
516         } else {
517             // incomplete
518             SkDebugf("!c");
519         }
520         if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) {
521             result = fPts[3] - fPts[0];
522         }
523     }
524     return result;
525 }
526 
527 // OPTIMIZE? share code with formulate_F1DotF2
528 // e.g. https://stackoverflow.com/a/35927917
findInflections(double tValues[2]) const529 int SkDCubic::findInflections(double tValues[2]) const {
530     double Ax = fPts[1].fX - fPts[0].fX;
531     double Ay = fPts[1].fY - fPts[0].fY;
532     double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX;
533     double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY;
534     double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX;
535     double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY;
536     return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues);
537 }
538 
formulate_F1DotF2(const double src[],double coeff[4])539 static void formulate_F1DotF2(const double src[], double coeff[4]) {
540     double a = src[2] - src[0];
541     double b = src[4] - 2 * src[2] + src[0];
542     double c = src[6] + 3 * (src[2] - src[4]) - src[0];
543     coeff[0] = c * c;
544     coeff[1] = 3 * b * c;
545     coeff[2] = 2 * b * b + c * a;
546     coeff[3] = a * b;
547 }
548 
549 /** SkDCubic'(t) = At^2 + Bt + C, where
550     A = 3(-a + 3(b - c) + d)
551     B = 6(a - 2b + c)
552     C = 3(b - a)
553     Solve for t, keeping only those that fit between 0 < t < 1
554 */
FindExtrema(const double src[],double tValues[2])555 int SkDCubic::FindExtrema(const double src[], double tValues[2]) {
556     // we divide A,B,C by 3 to simplify
557     double a = src[0];
558     double b = src[2];
559     double c = src[4];
560     double d = src[6];
561     double A = d - a + 3 * (b - c);
562     double B = 2 * (a - b - b + c);
563     double C = b - a;
564 
565     return SkDQuad::RootsValidT(A, B, C, tValues);
566 }
567 
568 /*  from SkGeometry.cpp
569     Looking for F' dot F'' == 0
570 
571     A = b - a
572     B = c - 2b + a
573     C = d - 3c + 3b - a
574 
575     F' = 3Ct^2 + 6Bt + 3A
576     F'' = 6Ct + 6B
577 
578     F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
579 */
findMaxCurvature(double tValues[]) const580 int SkDCubic::findMaxCurvature(double tValues[]) const {
581     double coeffX[4], coeffY[4];
582     int i;
583     formulate_F1DotF2(&fPts[0].fX, coeffX);
584     formulate_F1DotF2(&fPts[0].fY, coeffY);
585     for (i = 0; i < 4; i++) {
586         coeffX[i] = coeffX[i] + coeffY[i];
587     }
588     return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues);
589 }
590 
ptAtT(double t) const591 SkDPoint SkDCubic::ptAtT(double t) const {
592     if (0 == t) {
593         return fPts[0];
594     }
595     if (1 == t) {
596         return fPts[3];
597     }
598     double one_t = 1 - t;
599     double one_t2 = one_t * one_t;
600     double a = one_t2 * one_t;
601     double b = 3 * one_t2 * t;
602     double t2 = t * t;
603     double c = 3 * one_t * t2;
604     double d = t2 * t;
605     SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX,
606             a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY};
607     return result;
608 }
609 
610 /*
611  Given a cubic c, t1, and t2, find a small cubic segment.
612 
613  The new cubic is defined as points A, B, C, and D, where
614  s1 = 1 - t1
615  s2 = 1 - t2
616  A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1
617  D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2
618 
619  We don't have B or C. So We define two equations to isolate them.
620  First, compute two reference T values 1/3 and 2/3 from t1 to t2:
621 
622  c(at (2*t1 + t2)/3) == E
623  c(at (t1 + 2*t2)/3) == F
624 
625  Next, compute where those values must be if we know the values of B and C:
626 
627  _12   =  A*2/3 + B*1/3
628  12_   =  A*1/3 + B*2/3
629  _23   =  B*2/3 + C*1/3
630  23_   =  B*1/3 + C*2/3
631  _34   =  C*2/3 + D*1/3
632  34_   =  C*1/3 + D*2/3
633  _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
634  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
635  _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
636  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
637  _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3
638        =  A*8/27 + B*12/27 + C*6/27 + D*1/27
639        =  E
640  1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3
641        =  A*1/27 + B*6/27 + C*12/27 + D*8/27
642        =  F
643  E*27  =  A*8    + B*12   + C*6     + D
644  F*27  =  A      + B*6    + C*12    + D*8
645 
646 Group the known values on one side:
647 
648  M       = E*27 - A*8 - D     = B*12 + C* 6
649  N       = F*27 - A   - D*8   = B* 6 + C*12
650  M*2 - N = B*18
651  N*2 - M = C*18
652  B       = (M*2 - N)/18
653  C       = (N*2 - M)/18
654  */
655 
interp_cubic_coords(const double * src,double t)656 static double interp_cubic_coords(const double* src, double t) {
657     double ab = SkDInterp(src[0], src[2], t);
658     double bc = SkDInterp(src[2], src[4], t);
659     double cd = SkDInterp(src[4], src[6], t);
660     double abc = SkDInterp(ab, bc, t);
661     double bcd = SkDInterp(bc, cd, t);
662     double abcd = SkDInterp(abc, bcd, t);
663     return abcd;
664 }
665 
subDivide(double t1,double t2) const666 SkDCubic SkDCubic::subDivide(double t1, double t2) const {
667     if (t1 == 0 || t2 == 1) {
668         if (t1 == 0 && t2 == 1) {
669             return *this;
670         }
671         SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1);
672         SkDCubic dst = t1 == 0 ? pair.first() : pair.second();
673         return dst;
674     }
675     SkDCubic dst;
676     double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1);
677     double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1);
678     double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3);
679     double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3);
680     double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3);
681     double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3);
682     double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2);
683     double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2);
684     double mx = ex * 27 - ax * 8 - dx;
685     double my = ey * 27 - ay * 8 - dy;
686     double nx = fx * 27 - ax - dx * 8;
687     double ny = fy * 27 - ay - dy * 8;
688     /* bx = */ dst[1].fX = (mx * 2 - nx) / 18;
689     /* by = */ dst[1].fY = (my * 2 - ny) / 18;
690     /* cx = */ dst[2].fX = (nx * 2 - mx) / 18;
691     /* cy = */ dst[2].fY = (ny * 2 - my) / 18;
692     // FIXME: call align() ?
693     return dst;
694 }
695 
subDivide(const SkDPoint & a,const SkDPoint & d,double t1,double t2,SkDPoint dst[2]) const696 void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
697                          double t1, double t2, SkDPoint dst[2]) const {
698     SkASSERT(t1 != t2);
699     // this approach assumes that the control points computed directly are accurate enough
700     SkDCubic sub = subDivide(t1, t2);
701     dst[0] = sub[1] + (a - sub[0]);
702     dst[1] = sub[2] + (d - sub[3]);
703     if (t1 == 0 || t2 == 0) {
704         align(0, 1, t1 == 0 ? &dst[0] : &dst[1]);
705     }
706     if (t1 == 1 || t2 == 1) {
707         align(3, 2, t1 == 1 ? &dst[0] : &dst[1]);
708     }
709     if (AlmostBequalUlps(dst[0].fX, a.fX)) {
710         dst[0].fX = a.fX;
711     }
712     if (AlmostBequalUlps(dst[0].fY, a.fY)) {
713         dst[0].fY = a.fY;
714     }
715     if (AlmostBequalUlps(dst[1].fX, d.fX)) {
716         dst[1].fX = d.fX;
717     }
718     if (AlmostBequalUlps(dst[1].fY, d.fY)) {
719         dst[1].fY = d.fY;
720     }
721 }
722 
toFloatPoints(SkPoint * pts) const723 bool SkDCubic::toFloatPoints(SkPoint* pts) const {
724     const double* dCubic = &fPts[0].fX;
725     SkScalar* cubic = &pts[0].fX;
726     for (int index = 0; index < kPointCount * 2; ++index) {
727         cubic[index] = SkDoubleToScalar(dCubic[index]);
728         if (SkScalarAbs(cubic[index]) < FLT_EPSILON_ORDERABLE_ERR) {
729             cubic[index] = 0;
730         }
731     }
732     return SkIsFinite(&pts->fX, kPointCount * 2);
733 }
734 
top(const SkDCubic & dCurve,double startT,double endT,SkDPoint * topPt) const735 double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const {
736     double extremeTs[2];
737     double topT = -1;
738     int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs);
739     for (int index = 0; index < roots; ++index) {
740         double t = startT + (endT - startT) * extremeTs[index];
741         SkDPoint mid = dCurve.ptAtT(t);
742         if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) {
743             topT = t;
744             *topPt = mid;
745         }
746     }
747     return topT;
748 }
749 
intersectRay(SkIntersections * i,const SkDLine & line) const750 int SkTCubic::intersectRay(SkIntersections* i, const SkDLine& line) const {
751     return i->intersectRay(fCubic, line);
752 }
753 
hullIntersects(const SkDQuad & quad,bool * isLinear) const754 bool SkTCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const {
755     return quad.hullIntersects(fCubic, isLinear);
756 }
757 
hullIntersects(const SkDConic & conic,bool * isLinear) const758 bool SkTCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const  {
759     return conic.hullIntersects(fCubic, isLinear);
760 }
761 
setBounds(SkDRect * rect) const762 void SkTCubic::setBounds(SkDRect* rect) const {
763     rect->setBounds(fCubic);
764 }
765