xref: /aosp_15_r20/external/skia/tests/PathOpsCubicLineIntersectionIdeas.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2014 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 "include/core/SkTypes.h"
8 #include "include/private/base/SkDebug.h"
9 #include "src/base/SkRandom.h"
10 #include "src/pathops/SkPathOpsCubic.h"
11 #include "src/pathops/SkPathOpsPoint.h"
12 #include "src/pathops/SkPathOpsQuad.h"
13 #include "tests/PathOpsTestCommon.h"
14 #include "tests/Test.h"
15 
16 #include <algorithm>
17 #include <array>
18 #include <cmath>
19 
20 static bool gPathOpsCubicLineIntersectionIdeasVerbose = false;
21 
22 static struct CubicLineFailures {
23     CubicPts c;
24     double t;
25     SkDPoint p;
26 } cubicLineFailures[] = {
27     {{{{-164.3726806640625, 36.826904296875}, {-189.045166015625, -953.2220458984375},
28         {926.505859375, -897.36175537109375}, {-139.33489990234375, 204.40771484375}}},
29         0.37329583, {107.54935269006289, -632.13736293162208}},
30     {{{{784.056884765625, -554.8350830078125}, {67.5489501953125, 509.0224609375},
31         {-447.713134765625, 751.375}, {415.7784423828125, 172.22265625}}},
32         0.660005242, {-32.973148967736151, 478.01341797403569}},
33     {{{{-580.6834716796875, -127.044921875}, {-872.8983154296875, -945.54302978515625},
34         {260.8092041015625, -909.34991455078125}, {-976.2125244140625, -18.46551513671875}}},
35         0.578826774, {-390.17910153915489, -687.21144412296007}},
36 };
37 
38 int cubicLineFailuresCount = (int) std::size(cubicLineFailures);
39 
40 double measuredSteps[] = {
41     9.15910731e-007, 8.6600277e-007, 7.4122059e-007, 6.92087618e-007, 8.35290245e-007,
42     3.29763199e-007, 5.07547773e-007, 4.41294224e-007, 0, 0,
43     3.76879167e-006, 1.06126249e-006, 2.36873967e-006, 1.62421134e-005, 3.09103599e-005,
44     4.38917976e-005, 0.000112348938, 0.000243149242, 0.000433174114, 0.00170880232,
45     0.00272619724, 0.00518844604, 0.000352621078, 0.00175960064, 0.027875185,
46     0.0351329803, 0.103964925,
47 };
48 
49 /* last output : errors=3121
50     9.1796875e-007 8.59375e-007 7.5e-007 6.875e-007 8.4375e-007
51     3.125e-007 5e-007 4.375e-007 0 0
52     3.75e-006 1.09375e-006 2.1875e-006 1.640625e-005 3.0859375e-005
53     4.38964844e-005 0.000112304687 0.000243164063 0.000433181763 0.00170898437
54     0.00272619247 0.00518844604 0.000352621078 0.00175960064 0.027875185
55     0.0351329803 0.103964925
56 */
57 
binary_search(const SkDCubic & cubic,double step,const SkDPoint & pt,double t,int * iters)58 static double binary_search(const SkDCubic& cubic, double step, const SkDPoint& pt, double t,
59         int* iters) {
60     double firstStep = step;
61     do {
62         *iters += 1;
63         SkDPoint cubicAtT = cubic.ptAtT(t);
64         if (cubicAtT.approximatelyEqual(pt)) {
65             break;
66         }
67         double calcX = cubicAtT.fX - pt.fX;
68         double calcY = cubicAtT.fY - pt.fY;
69         double calcDist = calcX * calcX + calcY * calcY;
70         if (step == 0) {
71             SkDebugf("binary search failed: step=%1.9g cubic=", firstStep);
72             cubic.dump();
73             SkDebugf(" t=%1.9g ", t);
74             pt.dump();
75             SkDebugf("\n");
76             return -1;
77         }
78         double lastStep = step;
79         step /= 2;
80         SkDPoint lessPt = cubic.ptAtT(t - lastStep);
81         double lessX = lessPt.fX - pt.fX;
82         double lessY = lessPt.fY - pt.fY;
83         double lessDist = lessX * lessX + lessY * lessY;
84         // use larger x/y difference to choose step
85         if (calcDist > lessDist) {
86             t -= step;
87             t = std::max(0., t);
88         } else {
89             SkDPoint morePt = cubic.ptAtT(t + lastStep);
90             double moreX = morePt.fX - pt.fX;
91             double moreY = morePt.fY - pt.fY;
92             double moreDist = moreX * moreX + moreY * moreY;
93             if (calcDist <= moreDist) {
94                 continue;
95             }
96             t += step;
97             t = std::min(1., t);
98         }
99     } while (true);
100     return t;
101 }
102 
103 #if 0
104 static bool r2check(double A, double B, double C, double D, double* R2MinusQ3Ptr) {
105     if (approximately_zero(A)
106             && approximately_zero_when_compared_to(A, B)
107             && approximately_zero_when_compared_to(A, C)
108             && approximately_zero_when_compared_to(A, D)) {  // we're just a quadratic
109         return false;
110     }
111     if (approximately_zero_when_compared_to(D, A)
112             && approximately_zero_when_compared_to(D, B)
113             && approximately_zero_when_compared_to(D, C)) {  // 0 is one root
114         return false;
115     }
116     if (approximately_zero(A + B + C + D)) {  // 1 is one root
117         return false;
118     }
119     double a, b, c;
120     {
121         double invA = 1 / A;
122         a = B * invA;
123         b = C * invA;
124         c = D * invA;
125     }
126     double a2 = a * a;
127     double Q = (a2 - b * 3) / 9;
128     double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
129     double R2 = R * R;
130     double Q3 = Q * Q * Q;
131     double R2MinusQ3 = R2 - Q3;
132     *R2MinusQ3Ptr = R2MinusQ3;
133     return true;
134 }
135 #endif
136 
137 /* What is the relationship between the accuracy of the root in range and the magnitude of all
138    roots? To find out, create a bunch of cubics, and measure */
139 
DEF_TEST(PathOpsCubicLineRoots,reporter)140 DEF_TEST(PathOpsCubicLineRoots, reporter) {
141     if (!gPathOpsCubicLineIntersectionIdeasVerbose) {  // slow; exclude it by default
142         return;
143     }
144     SkRandom ran;
145     double worstStep[256] = {0};
146     int errors = 0;
147     int iters = 0;
148     double smallestR2 = 0;
149     double largestR2 = 0;
150     for (int index = 0; index < 1000000000; ++index) {
151         SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
152         CubicPts cuPts = {{origin,
153                 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
154                 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
155                 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}
156         }};
157         // construct a line at a known intersection
158         double t = ran.nextRangeF(0, 1);
159         SkDCubic cubic;
160         cubic.debugSet(cuPts.fPts);
161         SkDPoint pt = cubic.ptAtT(t);
162         // skip answers with no intersections (although note the bug!) or two, or more
163         // see if the line / cubic has a fun range of roots
164         double A, B, C, D;
165         SkDCubic::Coefficients(&cubic[0].fY, &A, &B, &C, &D);
166         D -= pt.fY;
167         double allRoots[3] = {0}, validRoots[3] = {0};
168         int realRoots = SkDCubic::RootsReal(A, B, C, D, allRoots);
169         int valid = SkDQuad::AddValidTs(allRoots, realRoots, validRoots);
170         if (valid != 1) {
171             continue;
172         }
173         if (realRoots == 1) {
174             continue;
175         }
176         t = validRoots[0];
177         SkDPoint calcPt = cubic.ptAtT(t);
178         if (calcPt.approximatelyEqual(pt)) {
179             continue;
180         }
181 #if 0
182         double R2MinusQ3;
183         if (r2check(A, B, C, D, &R2MinusQ3)) {
184             smallestR2 = std::min(smallestR2, R2MinusQ3);
185             largestR2 = std::max(largestR2, R2MinusQ3);
186         }
187 #endif
188         double largest = std::max(fabs(allRoots[0]), fabs(allRoots[1]));
189         if (realRoots == 3) {
190             largest = std::max(largest, fabs(allRoots[2]));
191         }
192         int largeBits;
193         if (largest <= 1) {
194 #if 0
195             SkDebugf("realRoots=%d (%1.9g, %1.9g, %1.9g) valid=%d (%1.9g, %1.9g, %1.9g)\n",
196                 realRoots, allRoots[0], allRoots[1], allRoots[2], valid, validRoots[0],
197                 validRoots[1], validRoots[2]);
198 #endif
199             double smallest = std::min(allRoots[0], allRoots[1]);
200             if (realRoots == 3) {
201                 smallest = std::min(smallest, allRoots[2]);
202             }
203             SkASSERT_RELEASE(smallest < 0);
204             SkASSERT_RELEASE(smallest >= -1);
205             largeBits = 0;
206         } else {
207             frexp(largest, &largeBits);
208             SkASSERT_RELEASE(largeBits >= 0);
209             SkASSERT_RELEASE(largeBits < 256);
210         }
211         double step = 1e-6;
212         if (largeBits > 21) {
213             step = 1e-1;
214         } else if (largeBits > 18) {
215             step = 1e-2;
216         } else if (largeBits > 15) {
217             step = 1e-3;
218         } else if (largeBits > 12) {
219             step = 1e-4;
220         } else if (largeBits > 9) {
221             step = 1e-5;
222         }
223         double diff;
224         do {
225             double newT = binary_search(cubic, step, pt, t, &iters);
226             if (newT >= 0) {
227                 diff = fabs(t - newT);
228                 break;
229             }
230             step *= 1.5;
231             SkASSERT_RELEASE(step < 1);
232         } while (true);
233         worstStep[largeBits] = std::max(worstStep[largeBits], diff);
234 #if 0
235         {
236             cubic.dump();
237             SkDebugf("\n");
238             SkDLine line = {{{pt.fX - 1, pt.fY}, {pt.fX + 1, pt.fY}}};
239             line.dump();
240             SkDebugf("\n");
241         }
242 #endif
243         ++errors;
244     }
245     SkDebugf("errors=%d avgIter=%1.9g", errors, (double) iters / errors);
246     SkDebugf(" steps: ");
247     int worstLimit = std::size(worstStep);
248     while (worstStep[--worstLimit] == 0) ;
249     for (int idx2 = 0; idx2 <= worstLimit; ++idx2) {
250         SkDebugf("%1.9g ", worstStep[idx2]);
251     }
252     SkDebugf("\n");
253     SkDebugf("smallestR2=%1.9g largestR2=%1.9g\n", smallestR2, largestR2);
254 }
255 
testOneFailure(const CubicLineFailures & failure)256 static double testOneFailure(const CubicLineFailures& failure) {
257     const CubicPts& c = failure.c;
258     SkDCubic cubic;
259     cubic.debugSet(c.fPts);
260     const SkDPoint& pt = failure.p;
261     double A, B, C, D;
262     SkDCubic::Coefficients(&cubic[0].fY, &A, &B, &C, &D);
263     D -= pt.fY;
264     double allRoots[3] = {0}, validRoots[3] = {0};
265     int realRoots = SkDCubic::RootsReal(A, B, C, D, allRoots);
266     int valid = SkDQuad::AddValidTs(allRoots, realRoots, validRoots);
267     SkASSERT_RELEASE(valid == 1);
268     SkASSERT_RELEASE(realRoots != 1);
269     double t = validRoots[0];
270     SkDPoint calcPt = cubic.ptAtT(t);
271     SkASSERT_RELEASE(!calcPt.approximatelyEqual(pt));
272     int iters = 0;
273     double newT = binary_search(cubic, 0.1, pt, t, &iters);
274     return newT;
275 }
276 
DEF_TEST(PathOpsCubicLineFailures,reporter)277 DEF_TEST(PathOpsCubicLineFailures, reporter) {
278     if ((false)) {  // disable for now
279         for (int index = 0; index < cubicLineFailuresCount; ++index) {
280             const CubicLineFailures& failure = cubicLineFailures[index];
281             double newT = testOneFailure(failure);
282             SkASSERT_RELEASE(newT >= 0);
283         }
284     }
285 }
286 
DEF_TEST(PathOpsCubicLineOneFailure,reporter)287 DEF_TEST(PathOpsCubicLineOneFailure, reporter) {
288     if ((false)) {  // disable for now
289         const CubicLineFailures& failure = cubicLineFailures[1];
290         double newT = testOneFailure(failure);
291         SkASSERT_RELEASE(newT >= 0);
292     }
293 }
294