• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 // from SkGeometry.cpp (and Numeric Solutions, 5.6)
379 // // TODO(skbug.com/14063) Deduplicate with SkCubics::RootsValidT
RootsValidT(double A,double B,double C,double D,double t[3])380 int 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);
384     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         }
403 nextRoot:
404         ;
405     }
406     return foundRoots;
407 }
408 
409 // TODO(skbug.com/14063) Deduplicate with SkCubics::RootsReal
RootsReal(double A,double B,double C,double D,double s[3])410 int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
411 #ifdef SK_DEBUG
412     #if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
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     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);
420     SkPathOpsDebug::MathematicaIze(str, sizeof(str));
421     SkDebugf("%s\n", str);
422     #endif
423 #endif
424     if (approximately_zero(A)
425             && approximately_zero_when_compared_to(A, B)
426             && approximately_zero_when_compared_to(A, C)
427             && approximately_zero_when_compared_to(A, D)) {  // we're just a quadratic
428         return SkDQuad::RootsReal(B, C, D, s);
429     }
430     if (approximately_zero_when_compared_to(D, A)
431             && approximately_zero_when_compared_to(D, B)
432             && approximately_zero_when_compared_to(D, C)) {  // 0 is one root
433         int num = SkDQuad::RootsReal(A, B, C, s);
434         for (int i = 0; i < num; ++i) {
435             if (approximately_zero(s[i])) {
436                 return num;
437             }
438         }
439         s[num++] = 0;
440         return num;
441     }
442     if (approximately_zero(A + B + C + D)) {  // 1 is one root
443         int num = SkDQuad::RootsReal(A, A + B, -D, s);
444         for (int i = 0; i < num; ++i) {
445             if (AlmostDequalUlps(s[i], 1)) {
446                 return num;
447             }
448         }
449         s[num++] = 1;
450         return num;
451     }
452     double a, b, c;
453     {
454         double invA = 1 / A;
455         a = B * invA;
456         b = C * invA;
457         c = D * invA;
458     }
459     double a2 = a * a;
460     double Q = (a2 - b * 3) / 9;
461     double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
462     double R2 = R * R;
463     double Q3 = Q * Q * Q;
464     double R2MinusQ3 = R2 - Q3;
465     double adiv3 = a / 3;
466     double r;
467     double* roots = s;
468     if (R2MinusQ3 < 0) {   // we have 3 real roots
469         // the divide/root can, due to finite precisions, be slightly outside of -1...1
470         double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.));
471         double neg2RootQ = -2 * sqrt(Q);
472 
473         r = neg2RootQ * cos(theta / 3) - adiv3;
474         *roots++ = r;
475 
476         r = neg2RootQ * cos((theta + 2 * SK_DoublePI) / 3) - adiv3;
477         if (!AlmostDequalUlps(s[0], r)) {
478             *roots++ = r;
479         }
480         r = neg2RootQ * cos((theta - 2 * SK_DoublePI) / 3) - adiv3;
481         if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) {
482             *roots++ = r;
483         }
484     } else {  // we have 1 real root
485         double sqrtR2MinusQ3 = sqrt(R2MinusQ3);
486         A = fabs(R) + sqrtR2MinusQ3;
487         A = std::cbrt(A); // cube root
488         if (R > 0) {
489             A = -A;
490         }
491         if (A != 0) {
492             A += Q / A;
493         }
494         r = A - adiv3;
495         *roots++ = r;
496         if (AlmostDequalUlps((double) R2, (double) Q3)) {
497             r = -A / 2 - adiv3;
498             if (!AlmostDequalUlps(s[0], r)) {
499                 *roots++ = r;
500             }
501         }
502     }
503     return static_cast<int>(roots - s);
504 }
505 
506 // OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
dxdyAtT(double t) const507 SkDVector SkDCubic::dxdyAtT(double t) const {
508     SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
509     if (result.fX == 0 && result.fY == 0) {
510         if (t == 0) {
511             result = fPts[2] - fPts[0];
512         } else if (t == 1) {
513             result = fPts[3] - fPts[1];
514         } else {
515             // incomplete
516             SkDebugf("!c");
517         }
518         if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) {
519             result = fPts[3] - fPts[0];
520         }
521     }
522     return result;
523 }
524 
525 // OPTIMIZE? share code with formulate_F1DotF2
526 // e.g. https://stackoverflow.com/a/35927917
findInflections(double tValues[2]) const527 int SkDCubic::findInflections(double tValues[2]) 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 
formulate_F1DotF2(const double src[],double coeff[4])537 static 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 */
FindExtrema(const double src[],double tValues[2])553 int SkDCubic::FindExtrema(const double src[], double tValues[2]) {
554     // we divide A,B,C by 3 to simplify
555     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);
561     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 */
findMaxCurvature(double tValues[]) const578 int 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 
ptAtT(double t) const589 SkDPoint SkDCubic::ptAtT(double t) const {
590     if (0 == t) {
591         return fPts[0];
592     }
593     if (1 == t) {
594         return fPts[3];
595     }
596     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 
644 Group 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 
interp_cubic_coords(const double * src,double t)654 static 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 
subDivide(double t1,double t2) const664 SkDCubic SkDCubic::subDivide(double t1, double t2) const {
665     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;
672     }
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;
690     // FIXME: call align() ?
691     return dst;
692 }
693 
subDivide(const SkDPoint & a,const SkDPoint & d,double t1,double t2,SkDPoint dst[2]) const694 void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
695                          double t1, double t2, SkDPoint dst[2]) const {
696     SkASSERT(t1 != t2);
697     // 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]);
701     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     }
707     if (AlmostBequalUlps(dst[0].fX, a.fX)) {
708         dst[0].fX = a.fX;
709     }
710     if (AlmostBequalUlps(dst[0].fY, a.fY)) {
711         dst[0].fY = a.fY;
712     }
713     if (AlmostBequalUlps(dst[1].fX, d.fX)) {
714         dst[1].fX = d.fX;
715     }
716     if (AlmostBequalUlps(dst[1].fY, d.fY)) {
717         dst[1].fY = d.fY;
718     }
719 }
720 
toFloatPoints(SkPoint * pts) const721 bool 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[index] = SkDoubleToScalar(dCubic[index]);
726         if (SkScalarAbs(cubic[index]) < FLT_EPSILON_ORDERABLE_ERR) {
727             cubic[index] = 0;
728         }
729     }
730     return SkIsFinite(&pts->fX, kPointCount * 2);
731 }
732 
top(const SkDCubic & dCurve,double startT,double endT,SkDPoint * topPt) const733 double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const {
734     double extremeTs[2];
735     double topT = -1;
736     int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs);
737     for (int index = 0; index < roots; ++index) {
738         double t = startT + (endT - startT) * extremeTs[index];
739         SkDPoint mid = dCurve.ptAtT(t);
740         if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) {
741             topT = t;
742             *topPt = mid;
743         }
744     }
745     return topT;
746 }
747 
intersectRay(SkIntersections * i,const SkDLine & line) const748 int SkTCubic::intersectRay(SkIntersections* i, const SkDLine& line) const {
749     return i->intersectRay(fCubic, line);
750 }
751 
hullIntersects(const SkDQuad & quad,bool * isLinear) const752 bool SkTCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const {
753     return quad.hullIntersects(fCubic, isLinear);
754 }
755 
hullIntersects(const SkDConic & conic,bool * isLinear) const756 bool SkTCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const  {
757     return conic.hullIntersects(fCubic, isLinear);
758 }
759 
setBounds(SkDRect * rect) const760 void SkTCubic::setBounds(SkDRect* rect) const {
761     rect->setBounds(fCubic);
762 }
763