Flutter Engine
The Flutter Engine
PathOpsCubicLineIntersectionIdeas.cpp
Go to the documentation of this file.
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 */
9#include "src/base/SkRandom.h"
14#include "tests/Test.h"
15
16#include <algorithm>
17#include <array>
18#include <cmath>
19
21
22static struct CubicLineFailures {
24 double t;
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
39
40double 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
58static 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
104static bool r2check(double A, double B, double C, double D, double* R2MinusQ3Ptr) {
108 && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic
109 return false;
110 }
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
140DEF_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);
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;
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
256static double testOneFailure(const CubicLineFailures& failure) {
257 const CubicPts& c = failure.c;
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);
272 int iters = 0;
273 double newT = binary_search(cubic, 0.1, pt, t, &iters);
274 return newT;
275}
276
277DEF_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
287DEF_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}
static int step(int x, SkScalar min, SkScalar max)
Definition: BlurTest.cpp:215
reporter
Definition: FontMgrTest.cpp:39
static double testOneFailure(const CubicLineFailures &failure)
static struct CubicLineFailures cubicLineFailures[]
DEF_TEST(PathOpsCubicLineRoots, reporter)
static bool gPathOpsCubicLineIntersectionIdeasVerbose
static double binary_search(const SkDCubic &cubic, double step, const SkDPoint &pt, double t, int *iters)
#define SkASSERT_RELEASE(cond)
Definition: SkAssert.h:100
static bool approximately_zero(double x)
Definition: SkCubics.cpp:153
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
bool approximately_zero_when_compared_to(double x, double y)
float nextRangeF(float min, float max)
Definition: SkRandom.h:64
#define C(TEST_CATEGORY)
Definition: colrv1.cpp:248
static bool b
struct MyStruct a[10]
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
#define R(r)
#define B
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
AI float cubic(float precision, const SkPoint pts[], const VectorXform &vectorXform=VectorXform())
Definition: WangsFormula.h:195
SkDPoint fPts[kPointCount]
static int RootsReal(double A, double B, double C, double D, double t[3])
static void Coefficients(const double *cubic, double *A, double *B, double *C, double *D)
bool approximatelyEqual(const SkDPoint &a) const
void dump() const
static int AddValidTs(double s[], int realRoots, double *t)