blob: 0b3623c5249e7de172caa5704050c25f6105dd9c [file] [log] [blame]
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001/*
2 * Copyright 2013 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 */
Mike Kleinc0bd9f92019-04-23 12:05:21 -05007#include "include/private/SkTArray.h"
8#include "include/utils/SkRandom.h"
John Stiles6e9ead92020-07-14 00:13:51 +00009#include "src/core/SkTSort.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050010#include "src/pathops/SkIntersections.h"
11#include "src/pathops/SkOpContour.h"
12#include "src/pathops/SkOpSegment.h"
13#include "tests/PathOpsTestCommon.h"
14#include "tests/Test.h"
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000015
16static bool gPathOpsAngleIdeasVerbose = false;
17static bool gPathOpsAngleIdeasEnableBruteCheck = false;
18
19class PathOpsAngleTester {
20public:
caryclark54359292015-03-26 07:52:43 -070021 static int ConvexHullOverlaps(SkOpAngle& lh, SkOpAngle& rh) {
22 return lh.convexHullOverlaps(&rh);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000023 }
24
caryclark54359292015-03-26 07:52:43 -070025 static int EndsIntersect(SkOpAngle& lh, SkOpAngle& rh) {
26 return lh.endsIntersect(&rh);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000027 }
28};
29
30struct TRange {
31 double tMin1;
32 double tMin2;
33 double t1;
34 double t2;
35 double tMin;
36 double a1;
37 double a2;
38 bool ccw;
39};
40
41static double testArc(skiatest::Reporter* reporter, const SkDQuad& quad, const SkDQuad& arcRef,
42 int octant) {
43 SkDQuad arc = arcRef;
44 SkDVector offset = {quad[0].fX, quad[0].fY};
45 arc[0] += offset;
46 arc[1] += offset;
47 arc[2] += offset;
48 SkIntersections i;
49 i.intersect(arc, quad);
50 if (i.used() == 0) {
51 return -1;
52 }
53 int smallest = -1;
54 double t = 2;
55 for (int idx = 0; idx < i.used(); ++idx) {
56 if (i[0][idx] > 1 || i[0][idx] < 0) {
57 i.reset();
58 i.intersect(arc, quad);
59 }
60 if (i[1][idx] > 1 || i[1][idx] < 0) {
61 i.reset();
62 i.intersect(arc, quad);
63 }
64 if (t > i[1][idx]) {
65 smallest = idx;
66 t = i[1][idx];
67 }
68 }
69 REPORTER_ASSERT(reporter, smallest >= 0);
70 REPORTER_ASSERT(reporter, t >= 0 && t <= 1);
71 return i[1][smallest];
72}
73
74static void orderQuads(skiatest::Reporter* reporter, const SkDQuad& quad, double radius,
75 SkTArray<double, false>* tArray) {
76 double r = radius;
77 double s = r * SK_ScalarTanPIOver8;
78 double m = r * SK_ScalarRoot2Over2;
79 // construct circle from quads
caryclarka35ab3e2016-10-20 08:32:18 -070080 const QuadPts circle[8] = {{{{ r, 0}, { r, -s}, { m, -m}}},
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000081 {{{ m, -m}, { s, -r}, { 0, -r}}},
82 {{{ 0, -r}, {-s, -r}, {-m, -m}}},
83 {{{-m, -m}, {-r, -s}, {-r, 0}}},
84 {{{-r, 0}, {-r, s}, {-m, m}}},
85 {{{-m, m}, {-s, r}, { 0, r}}},
86 {{{ 0, r}, { s, r}, { m, m}}},
87 {{{ m, m}, { r, s}, { r, 0}}}};
88 for (int octant = 0; octant < 8; ++octant) {
caryclarka35ab3e2016-10-20 08:32:18 -070089 SkDQuad cQuad;
90 cQuad.debugSet(circle[octant].fPts);
91 double t = testArc(reporter, quad, cQuad, octant);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000092 if (t < 0) {
93 continue;
94 }
95 for (int index = 0; index < tArray->count(); ++index) {
96 double matchT = (*tArray)[index];
97 if (approximately_equal(t, matchT)) {
98 goto next;
99 }
100 }
101 tArray->push_back(t);
102next: ;
103 }
104}
105
106static double quadAngle(skiatest::Reporter* reporter, const SkDQuad& quad, double t) {
107 const SkDVector& pt = quad.ptAtT(t) - quad[0];
108 double angle = (atan2(pt.fY, pt.fX) + SK_ScalarPI) * 8 / (SK_ScalarPI * 2);
109 REPORTER_ASSERT(reporter, angle >= 0 && angle <= 8);
110 return angle;
111}
112
113static bool angleDirection(double a1, double a2) {
114 double delta = a1 - a2;
115 return (delta < 4 && delta > 0) || delta < -4;
116}
117
118static void setQuadHullSweep(const SkDQuad& quad, SkDVector sweep[2]) {
119 sweep[0] = quad[1] - quad[0];
120 sweep[1] = quad[2] - quad[0];
121}
122
123static double distEndRatio(double dist, const SkDQuad& quad) {
124 SkDVector v[] = {quad[2] - quad[0], quad[1] - quad[0], quad[2] - quad[1]};
Brian Osman788b9162020-02-07 10:36:46 -0500125 double longest = std::max(v[0].length(), std::max(v[1].length(), v[2].length()));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000126 return longest / dist;
127}
128
129static bool checkParallel(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2) {
130 SkDVector sweep[2], tweep[2];
131 setQuadHullSweep(quad1, sweep);
132 setQuadHullSweep(quad2, tweep);
133 // if the ctrl tangents are not nearly parallel, use them
134 // solve for opposite direction displacement scale factor == m
135 // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x
136 // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1]
137 // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x)
138 // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x)
139 // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x
140 // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y)
141 // m = v1.cross(v2) / v1.dot(v2)
142 double s0dt0 = sweep[0].dot(tweep[0]);
143 REPORTER_ASSERT(reporter, s0dt0 != 0);
144 double s0xt0 = sweep[0].crossCheck(tweep[0]);
145 double m = s0xt0 / s0dt0;
146 double sDist = sweep[0].length() * m;
147 double tDist = tweep[0].length() * m;
148 bool useS = fabs(sDist) < fabs(tDist);
149 double mFactor = fabs(useS ? distEndRatio(sDist, quad1) : distEndRatio(tDist, quad2));
150 if (mFactor < 5000) { // empirically found limit
151 return s0xt0 < 0;
152 }
153 SkDVector m0 = quad1.ptAtT(0.5) - quad1[0];
154 SkDVector m1 = quad2.ptAtT(0.5) - quad2[0];
155 return m0.crossCheck(m1) < 0;
156}
157
158/* returns
159 -1 if overlaps
160 0 if no overlap cw
161 1 if no overlap ccw
162*/
163static int quadHullsOverlap(skiatest::Reporter* reporter, const SkDQuad& quad1,
164 const SkDQuad& quad2) {
165 SkDVector sweep[2], tweep[2];
166 setQuadHullSweep(quad1, sweep);
167 setQuadHullSweep(quad2, tweep);
168 double s0xs1 = sweep[0].crossCheck(sweep[1]);
169 double s0xt0 = sweep[0].crossCheck(tweep[0]);
170 double s1xt0 = sweep[1].crossCheck(tweep[0]);
171 bool tBetweenS = s0xs1 > 0 ? s0xt0 > 0 && s1xt0 < 0 : s0xt0 < 0 && s1xt0 > 0;
172 double s0xt1 = sweep[0].crossCheck(tweep[1]);
173 double s1xt1 = sweep[1].crossCheck(tweep[1]);
174 tBetweenS |= s0xs1 > 0 ? s0xt1 > 0 && s1xt1 < 0 : s0xt1 < 0 && s1xt1 > 0;
175 double t0xt1 = tweep[0].crossCheck(tweep[1]);
176 if (tBetweenS) {
177 return -1;
178 }
179 if ((s0xt0 == 0 && s1xt1 == 0) || (s1xt0 == 0 && s0xt1 == 0)) { // s0 to s1 equals t0 to t1
180 return -1;
181 }
182 bool sBetweenT = t0xt1 > 0 ? s0xt0 < 0 && s0xt1 > 0 : s0xt0 > 0 && s0xt1 < 0;
183 sBetweenT |= t0xt1 > 0 ? s1xt0 < 0 && s1xt1 > 0 : s1xt0 > 0 && s1xt1 < 0;
184 if (sBetweenT) {
185 return -1;
186 }
187 // if all of the sweeps are in the same half plane, then the order of any pair is enough
188 if (s0xt0 >= 0 && s0xt1 >= 0 && s1xt0 >= 0 && s1xt1 >= 0) {
189 return 0;
190 }
191 if (s0xt0 <= 0 && s0xt1 <= 0 && s1xt0 <= 0 && s1xt1 <= 0) {
192 return 1;
193 }
194 // if the outside sweeps are greater than 180 degress:
195 // first assume the inital tangents are the ordering
196 // if the midpoint direction matches the inital order, that is enough
197 SkDVector m0 = quad1.ptAtT(0.5) - quad1[0];
198 SkDVector m1 = quad2.ptAtT(0.5) - quad2[0];
199 double m0xm1 = m0.crossCheck(m1);
200 if (s0xt0 > 0 && m0xm1 > 0) {
201 return 0;
202 }
203 if (s0xt0 < 0 && m0xm1 < 0) {
204 return 1;
205 }
206 REPORTER_ASSERT(reporter, s0xt0 != 0);
207 return checkParallel(reporter, quad1, quad2);
208}
209
210static double radianSweep(double start, double end) {
211 double sweep = end - start;
212 if (sweep > SK_ScalarPI) {
213 sweep -= 2 * SK_ScalarPI;
214 } else if (sweep < -SK_ScalarPI) {
215 sweep += 2 * SK_ScalarPI;
216 }
217 return sweep;
218}
219
220static bool radianBetween(double start, double test, double end) {
221 double startToEnd = radianSweep(start, end);
222 double startToTest = radianSweep(start, test);
223 double testToEnd = radianSweep(test, end);
224 return (startToTest <= 0 && testToEnd <= 0 && startToTest >= startToEnd) ||
225 (startToTest >= 0 && testToEnd >= 0 && startToTest <= startToEnd);
226}
227
228static bool orderTRange(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
229 double r, TRange* result) {
230 SkTArray<double, false> t1Array, t2Array;
231 orderQuads(reporter, quad1, r, &t1Array);
John Stiles6e9ead92020-07-14 00:13:51 +0000232 orderQuads(reporter,quad2, r, &t2Array);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000233 if (!t1Array.count() || !t2Array.count()) {
234 return false;
235 }
John Stiles886a9042020-07-14 16:28:33 -0400236 SkTQSort<double>(t1Array.begin(), t1Array.end());
237 SkTQSort<double>(t2Array.begin(), t2Array.end());
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000238 double t1 = result->tMin1 = t1Array[0];
239 double t2 = result->tMin2 = t2Array[0];
240 double a1 = quadAngle(reporter,quad1, t1);
241 double a2 = quadAngle(reporter,quad2, t2);
242 if (approximately_equal(a1, a2)) {
243 return false;
244 }
245 bool refCCW = angleDirection(a1, a2);
246 result->t1 = t1;
247 result->t2 = t2;
Brian Osman788b9162020-02-07 10:36:46 -0500248 result->tMin = std::min(t1, t2);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000249 result->a1 = a1;
250 result->a2 = a2;
251 result->ccw = refCCW;
252 return true;
253}
254
255static bool equalPoints(const SkDPoint& pt1, const SkDPoint& pt2, double max) {
256 return approximately_zero_when_compared_to(pt1.fX - pt2.fX, max)
257 && approximately_zero_when_compared_to(pt1.fY - pt2.fY, max);
258}
259
260static double maxDist(const SkDQuad& quad) {
261 SkDRect bounds;
262 bounds.setBounds(quad);
263 SkDVector corner[4] = {
264 { bounds.fLeft - quad[0].fX, bounds.fTop - quad[0].fY },
265 { bounds.fRight - quad[0].fX, bounds.fTop - quad[0].fY },
266 { bounds.fLeft - quad[0].fX, bounds.fBottom - quad[0].fY },
267 { bounds.fRight - quad[0].fX, bounds.fBottom - quad[0].fY }
268 };
269 double max = 0;
270 for (unsigned index = 0; index < SK_ARRAY_COUNT(corner); ++index) {
Brian Osman788b9162020-02-07 10:36:46 -0500271 max = std::max(max, corner[index].length());
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000272 }
273 return max;
274}
275
276static double maxQuad(const SkDQuad& quad) {
277 double max = 0;
278 for (int index = 0; index < 2; ++index) {
Brian Osman788b9162020-02-07 10:36:46 -0500279 max = std::max(max, fabs(quad[index].fX));
280 max = std::max(max, fabs(quad[index].fY));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000281 }
282 return max;
283}
284
285static bool bruteMinT(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
286 TRange* lowerRange, TRange* upperRange) {
Brian Osman788b9162020-02-07 10:36:46 -0500287 double maxRadius = std::min(maxDist(quad1), maxDist(quad2));
288 double maxQuads = std::max(maxQuad(quad1), maxQuad(quad2));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000289 double r = maxRadius / 2;
290 double rStep = r / 2;
291 SkDPoint best1 = {SK_ScalarInfinity, SK_ScalarInfinity};
292 SkDPoint best2 = {SK_ScalarInfinity, SK_ScalarInfinity};
293 int bestCCW = -1;
294 double bestR = maxRadius;
295 upperRange->tMin = 0;
296 lowerRange->tMin = 1;
297 do {
298 do { // find upper bounds of single result
299 TRange tRange;
300 bool stepUp = orderTRange(reporter, quad1, quad2, r, &tRange);
301 if (stepUp) {
302 SkDPoint pt1 = quad1.ptAtT(tRange.t1);
303 if (equalPoints(pt1, best1, maxQuads)) {
304 break;
305 }
306 best1 = pt1;
307 SkDPoint pt2 = quad2.ptAtT(tRange.t2);
308 if (equalPoints(pt2, best2, maxQuads)) {
309 break;
310 }
311 best2 = pt2;
312 if (gPathOpsAngleIdeasVerbose) {
313 SkDebugf("u bestCCW=%d ccw=%d bestMin=%1.9g:%1.9g r=%1.9g tMin=%1.9g\n",
314 bestCCW, tRange.ccw, lowerRange->tMin, upperRange->tMin, r,
315 tRange.tMin);
316 }
317 if (bestCCW >= 0 && bestCCW != (int) tRange.ccw) {
318 if (tRange.tMin < upperRange->tMin) {
319 upperRange->tMin = 0;
320 } else {
321 stepUp = false;
322 }
323 }
324 if (upperRange->tMin < tRange.tMin) {
325 bestCCW = tRange.ccw;
326 bestR = r;
327 *upperRange = tRange;
328 }
329 if (lowerRange->tMin > tRange.tMin) {
330 *lowerRange = tRange;
331 }
332 }
333 r += stepUp ? rStep : -rStep;
334 rStep /= 2;
335 } while (rStep > FLT_EPSILON);
336 if (bestCCW < 0) {
337 REPORTER_ASSERT(reporter, bestR < maxRadius);
338 return false;
339 }
340 double lastHighR = bestR;
341 r = bestR / 2;
342 rStep = r / 2;
343 do { // find lower bounds of single result
344 TRange tRange;
345 bool success = orderTRange(reporter, quad1, quad2, r, &tRange);
346 if (success) {
347 if (gPathOpsAngleIdeasVerbose) {
348 SkDebugf("l bestCCW=%d ccw=%d bestMin=%1.9g:%1.9g r=%1.9g tMin=%1.9g\n",
349 bestCCW, tRange.ccw, lowerRange->tMin, upperRange->tMin, r,
350 tRange.tMin);
351 }
352 if (bestCCW != (int) tRange.ccw || upperRange->tMin < tRange.tMin) {
353 bestCCW = tRange.ccw;
354 *upperRange = tRange;
355 bestR = lastHighR;
356 break; // need to establish a new upper bounds
357 }
358 SkDPoint pt1 = quad1.ptAtT(tRange.t1);
359 SkDPoint pt2 = quad2.ptAtT(tRange.t2);
360 if (equalPoints(pt1, best1, maxQuads)) {
361 goto breakOut;
362 }
363 best1 = pt1;
364 if (equalPoints(pt2, best2, maxQuads)) {
365 goto breakOut;
366 }
367 best2 = pt2;
368 if (equalPoints(pt1, pt2, maxQuads)) {
369 success = false;
370 } else {
371 if (upperRange->tMin < tRange.tMin) {
372 *upperRange = tRange;
373 }
374 if (lowerRange->tMin > tRange.tMin) {
375 *lowerRange = tRange;
376 }
377 }
Brian Osman788b9162020-02-07 10:36:46 -0500378 lastHighR = std::min(r, lastHighR);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000379 }
380 r += success ? -rStep : rStep;
381 rStep /= 2;
382 } while (rStep > FLT_EPSILON);
383 } while (rStep > FLT_EPSILON);
384breakOut:
385 if (gPathOpsAngleIdeasVerbose) {
386 SkDebugf("l a2-a1==%1.9g\n", lowerRange->a2 - lowerRange->a1);
387 }
388 return true;
389}
390
391static void bruteForce(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
392 bool ccw) {
393 if (!gPathOpsAngleIdeasEnableBruteCheck) {
394 return;
395 }
396 TRange lowerRange, upperRange;
397 bool result = bruteMinT(reporter, quad1, quad2, &lowerRange, &upperRange);
398 REPORTER_ASSERT(reporter, result);
399 double angle = fabs(lowerRange.a2 - lowerRange.a1);
400 REPORTER_ASSERT(reporter, angle > 3.998 || ccw == upperRange.ccw);
401}
402
403static bool bruteForceCheck(skiatest::Reporter* reporter, const SkDQuad& quad1,
404 const SkDQuad& quad2, bool ccw) {
405 TRange lowerRange, upperRange;
406 bool result = bruteMinT(reporter, quad1, quad2, &lowerRange, &upperRange);
407 REPORTER_ASSERT(reporter, result);
408 return ccw == upperRange.ccw;
409}
410
caryclark55888e42016-07-18 10:01:36 -0700411static void makeSegment(SkOpContour* contour, const SkDQuad& quad, SkPoint shortQuad[3]) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000412 shortQuad[0] = quad[0].asSkPoint();
413 shortQuad[1] = quad[1].asSkPoint();
414 shortQuad[2] = quad[2].asSkPoint();
caryclark55888e42016-07-18 10:01:36 -0700415 contour->addQuad(shortQuad);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000416}
417
418static void testQuadAngles(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
Herb Derbyc3cc5fa2017-03-07 11:11:47 -0500419 int testNo, SkArenaAlloc* allocator) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000420 SkPoint shortQuads[2][3];
caryclark54359292015-03-26 07:52:43 -0700421
caryclark624637c2015-05-11 07:21:27 -0700422 SkOpContourHead contour;
caryclark55888e42016-07-18 10:01:36 -0700423 SkOpGlobalState state(&contour, allocator SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr));
caryclark54359292015-03-26 07:52:43 -0700424 contour.init(&state, false, false);
caryclark55888e42016-07-18 10:01:36 -0700425 makeSegment(&contour, quad1, shortQuads[0]);
426 makeSegment(&contour, quad1, shortQuads[1]);
caryclark54359292015-03-26 07:52:43 -0700427 SkOpSegment* seg1 = contour.first();
caryclark55888e42016-07-18 10:01:36 -0700428 seg1->debugAddAngle(0, 1);
caryclark54359292015-03-26 07:52:43 -0700429 SkOpSegment* seg2 = seg1->next();
caryclark55888e42016-07-18 10:01:36 -0700430 seg2->debugAddAngle(0, 1);
caryclark54359292015-03-26 07:52:43 -0700431 int realOverlap = PathOpsAngleTester::ConvexHullOverlaps(*seg1->debugLastAngle(),
432 *seg2->debugLastAngle());
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000433 const SkDPoint& origin = quad1[0];
434 REPORTER_ASSERT(reporter, origin == quad2[0]);
435 double a1s = atan2(origin.fY - quad1[1].fY, quad1[1].fX - origin.fX);
436 double a1e = atan2(origin.fY - quad1[2].fY, quad1[2].fX - origin.fX);
437 double a2s = atan2(origin.fY - quad2[1].fY, quad2[1].fX - origin.fX);
438 double a2e = atan2(origin.fY - quad2[2].fY, quad2[2].fX - origin.fX);
439 bool oldSchoolOverlap = radianBetween(a1s, a2s, a1e)
440 || radianBetween(a1s, a2e, a1e) || radianBetween(a2s, a1s, a2e)
441 || radianBetween(a2s, a1e, a2e);
442 int overlap = quadHullsOverlap(reporter, quad1, quad2);
443 bool realMatchesOverlap = realOverlap == overlap || SK_ScalarPI - fabs(a2s - a1s) < 0.002;
444 if (realOverlap != overlap) {
445 SkDebugf("\nSK_ScalarPI - fabs(a2s - a1s) = %1.9g\n", SK_ScalarPI - fabs(a2s - a1s));
446 }
447 if (!realMatchesOverlap) {
448 DumpQ(quad1, quad2, testNo);
449 }
450 REPORTER_ASSERT(reporter, realMatchesOverlap);
451 if (oldSchoolOverlap != (overlap < 0)) {
452 overlap = quadHullsOverlap(reporter, quad1, quad2); // set a breakpoint and debug if assert fires
453 REPORTER_ASSERT(reporter, oldSchoolOverlap == (overlap < 0));
454 }
455 SkDVector v1s = quad1[1] - quad1[0];
456 SkDVector v1e = quad1[2] - quad1[0];
457 SkDVector v2s = quad2[1] - quad2[0];
458 SkDVector v2e = quad2[2] - quad2[0];
459 double vDir[2] = { v1s.cross(v1e), v2s.cross(v2e) };
460 bool ray1In2 = v1s.cross(v2s) * vDir[1] <= 0 && v1s.cross(v2e) * vDir[1] >= 0;
461 bool ray2In1 = v2s.cross(v1s) * vDir[0] <= 0 && v2s.cross(v1e) * vDir[0] >= 0;
462 if (overlap >= 0) {
463 // verify that hulls really don't overlap
464 REPORTER_ASSERT(reporter, !ray1In2);
465 REPORTER_ASSERT(reporter, !ray2In1);
466 bool ctrl1In2 = v1e.cross(v2s) * vDir[1] <= 0 && v1e.cross(v2e) * vDir[1] >= 0;
467 REPORTER_ASSERT(reporter, !ctrl1In2);
468 bool ctrl2In1 = v2e.cross(v1s) * vDir[0] <= 0 && v2e.cross(v1e) * vDir[0] >= 0;
469 REPORTER_ASSERT(reporter, !ctrl2In1);
470 // check answer against reference
471 bruteForce(reporter, quad1, quad2, overlap > 0);
472 }
473 // continue end point rays and see if they intersect the opposite curve
474 SkDLine rays[] = {{{origin, quad2[2]}}, {{origin, quad1[2]}}};
475 const SkDQuad* quads[] = {&quad1, &quad2};
476 SkDVector midSpokes[2];
477 SkIntersections intersect[2];
478 double minX, minY, maxX, maxY;
479 minX = minY = SK_ScalarInfinity;
480 maxX = maxY = -SK_ScalarInfinity;
481 double maxWidth = 0;
482 bool useIntersect = false;
483 double smallestTs[] = {1, 1};
484 for (unsigned index = 0; index < SK_ARRAY_COUNT(quads); ++index) {
485 const SkDQuad& q = *quads[index];
486 midSpokes[index] = q.ptAtT(0.5) - origin;
Brian Osman788b9162020-02-07 10:36:46 -0500487 minX = std::min(std::min(std::min(minX, origin.fX), q[1].fX), q[2].fX);
488 minY = std::min(std::min(std::min(minY, origin.fY), q[1].fY), q[2].fY);
489 maxX = std::max(std::max(std::max(maxX, origin.fX), q[1].fX), q[2].fX);
490 maxY = std::max(std::max(std::max(maxY, origin.fY), q[1].fY), q[2].fY);
491 maxWidth = std::max(maxWidth, std::max(maxX - minX, maxY - minY));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000492 intersect[index].intersectRay(q, rays[index]);
493 const SkIntersections& i = intersect[index];
494 REPORTER_ASSERT(reporter, i.used() >= 1);
495 bool foundZero = false;
496 double smallT = 1;
497 for (int idx2 = 0; idx2 < i.used(); ++idx2) {
498 double t = i[0][idx2];
499 if (t == 0) {
500 foundZero = true;
501 continue;
502 }
503 if (smallT > t) {
504 smallT = t;
505 }
506 }
507 REPORTER_ASSERT(reporter, foundZero == true);
508 if (smallT == 1) {
509 continue;
510 }
511 SkDVector ray = q.ptAtT(smallT) - origin;
512 SkDVector end = rays[index][1] - origin;
513 if (ray.fX * end.fX < 0 || ray.fY * end.fY < 0) {
514 continue;
515 }
516 double rayDist = ray.length();
517 double endDist = end.length();
518 double delta = fabs(rayDist - endDist) / maxWidth;
519 if (delta > 1e-4) {
520 useIntersect ^= true;
521 }
522 smallestTs[index] = smallT;
523 }
524 bool firstInside;
525 if (useIntersect) {
526 int sIndex = (int) (smallestTs[1] < 1);
527 REPORTER_ASSERT(reporter, smallestTs[sIndex ^ 1] == 1);
528 double t = smallestTs[sIndex];
529 const SkDQuad& q = *quads[sIndex];
530 SkDVector ray = q.ptAtT(t) - origin;
531 SkDVector end = rays[sIndex][1] - origin;
532 double rayDist = ray.length();
533 double endDist = end.length();
534 SkDVector mid = q.ptAtT(t / 2) - origin;
535 double midXray = mid.crossCheck(ray);
536 if (gPathOpsAngleIdeasVerbose) {
537 SkDebugf("rayDist>endDist:%d sIndex==0:%d vDir[sIndex]<0:%d midXray<0:%d\n",
538 rayDist > endDist, sIndex == 0, vDir[sIndex] < 0, midXray < 0);
539 }
540 SkASSERT(SkScalarSignAsInt(SkDoubleToScalar(midXray))
541 == SkScalarSignAsInt(SkDoubleToScalar(vDir[sIndex])));
542 firstInside = (rayDist > endDist) ^ (sIndex == 0) ^ (vDir[sIndex] < 0);
543 } else if (overlap >= 0) {
544 return; // answer has already been determined
545 } else {
546 firstInside = checkParallel(reporter, quad1, quad2);
547 }
548 if (overlap < 0) {
549 SkDEBUGCODE(int realEnds =)
caryclark54359292015-03-26 07:52:43 -0700550 PathOpsAngleTester::EndsIntersect(*seg1->debugLastAngle(),
551 *seg2->debugLastAngle());
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000552 SkASSERT(realEnds == (firstInside ? 1 : 0));
553 }
554 bruteForce(reporter, quad1, quad2, firstInside);
555}
556
557DEF_TEST(PathOpsAngleOverlapHullsOne, reporter) {
Florin Malita14a64302017-05-24 14:53:44 -0400558 SkSTArenaAlloc<4096> allocator;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000559// gPathOpsAngleIdeasVerbose = true;
caryclarka35ab3e2016-10-20 08:32:18 -0700560 const QuadPts quads[] = {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000561{{{939.4808349609375, 914.355224609375}, {-357.7921142578125, 590.842529296875}, {736.8936767578125, -350.717529296875}}},
562{{{939.4808349609375, 914.355224609375}, {-182.85418701171875, 634.4552001953125}, {-509.62615966796875, 576.1182861328125}}}
563 };
564 for (int index = 0; index < (int) SK_ARRAY_COUNT(quads); index += 2) {
caryclarka35ab3e2016-10-20 08:32:18 -0700565 SkDQuad quad0, quad1;
566 quad0.debugSet(quads[index].fPts);
567 quad1.debugSet(quads[index + 1].fPts);
568 testQuadAngles(reporter, quad0, quad1, 0, &allocator);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000569 }
570}
571
572DEF_TEST(PathOpsAngleOverlapHulls, reporter) {
Florin Malita14a64302017-05-24 14:53:44 -0400573 SkSTArenaAlloc<4096> allocator;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000574 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
575 return;
576 }
577 SkRandom ran;
578 for (int index = 0; index < 100000; ++index) {
579 if (index % 1000 == 999) SkDebugf(".");
580 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
caryclarka35ab3e2016-10-20 08:32:18 -0700581 QuadPts quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000582 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
caryclarka35ab3e2016-10-20 08:32:18 -0700583 if (quad1.fPts[0] == quad1.fPts[2]) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000584 continue;
585 }
caryclarka35ab3e2016-10-20 08:32:18 -0700586 QuadPts quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000587 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
caryclarka35ab3e2016-10-20 08:32:18 -0700588 if (quad2.fPts[0] == quad2.fPts[2]) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000589 continue;
590 }
591 SkIntersections i;
caryclarka35ab3e2016-10-20 08:32:18 -0700592 SkDQuad q1, q2;
593 q1.debugSet(quad1.fPts);
594 q2.debugSet(quad2.fPts);
595 i.intersect(q1, q2);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000596 REPORTER_ASSERT(reporter, i.used() >= 1);
597 if (i.used() > 1) {
598 continue;
599 }
caryclarka35ab3e2016-10-20 08:32:18 -0700600 testQuadAngles(reporter, q1, q2, index, &allocator);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000601 }
602}
603
604DEF_TEST(PathOpsAngleBruteT, reporter) {
605 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
606 return;
607 }
608 SkRandom ran;
609 double smaller = SK_Scalar1;
610 SkDQuad small[2];
Kevin Lubick42846132018-01-05 10:11:11 -0500611 SkDEBUGCODE(int smallIndex = 0);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000612 for (int index = 0; index < 100000; ++index) {
613 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
caryclarka35ab3e2016-10-20 08:32:18 -0700614 QuadPts quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000615 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
caryclarka35ab3e2016-10-20 08:32:18 -0700616 if (quad1.fPts[0] == quad1.fPts[2]) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000617 continue;
618 }
caryclarka35ab3e2016-10-20 08:32:18 -0700619 QuadPts quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000620 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
caryclarka35ab3e2016-10-20 08:32:18 -0700621 if (quad2.fPts[0] == quad2.fPts[2]) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000622 continue;
623 }
caryclarka35ab3e2016-10-20 08:32:18 -0700624 SkDQuad q1, q2;
625 q1.debugSet(quad1.fPts);
626 q2.debugSet(quad2.fPts);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000627 SkIntersections i;
caryclarka35ab3e2016-10-20 08:32:18 -0700628 i.intersect(q1, q2);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000629 REPORTER_ASSERT(reporter, i.used() >= 1);
630 if (i.used() > 1) {
631 continue;
632 }
633 TRange lowerRange, upperRange;
caryclarka35ab3e2016-10-20 08:32:18 -0700634 bool result = bruteMinT(reporter, q1, q2, &lowerRange, &upperRange);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000635 REPORTER_ASSERT(reporter, result);
Brian Osman788b9162020-02-07 10:36:46 -0500636 double min = std::min(upperRange.t1, upperRange.t2);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000637 if (smaller > min) {
caryclarka35ab3e2016-10-20 08:32:18 -0700638 small[0] = q1;
639 small[1] = q2;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000640 SkDEBUGCODE(smallIndex = index);
641 smaller = min;
642 }
643 }
644#ifdef SK_DEBUG
645 DumpQ(small[0], small[1], smallIndex);
646#endif
647}
648
649DEF_TEST(PathOpsAngleBruteTOne, reporter) {
650// gPathOpsAngleIdeasVerbose = true;
caryclarka35ab3e2016-10-20 08:32:18 -0700651 const QuadPts qPts[] = {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000652{{{-770.8492431640625, 948.2369384765625}, {-853.37066650390625, 972.0301513671875}, {-200.62042236328125, -26.7174072265625}}},
653{{{-770.8492431640625, 948.2369384765625}, {513.602783203125, 578.8681640625}, {960.641357421875, -813.69757080078125}}},
654{{{563.8267822265625, -107.4566650390625}, {-44.67724609375, -136.57452392578125}, {492.3856201171875, -268.79644775390625}}},
655{{{563.8267822265625, -107.4566650390625}, {708.049072265625, -100.77789306640625}, {-48.88226318359375, 967.9022216796875}}},
656{{{598.857421875, 846.345458984375}, {-644.095703125, -316.12921142578125}, {-97.64599609375, 20.6158447265625}}},
657{{{598.857421875, 846.345458984375}, {715.7142333984375, 955.3599853515625}, {-919.9478759765625, 691.611328125}}},
658 };
659 TRange lowerRange, upperRange;
caryclarka35ab3e2016-10-20 08:32:18 -0700660 SkDQuad quads[SK_ARRAY_COUNT(qPts)];
661 for (int index = 0; index < (int) SK_ARRAY_COUNT(qPts); ++index) {
662 quads[index].debugSet(qPts[index].fPts);
663 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000664 bruteMinT(reporter, quads[0], quads[1], &lowerRange, &upperRange);
665 bruteMinT(reporter, quads[2], quads[3], &lowerRange, &upperRange);
666 bruteMinT(reporter, quads[4], quads[5], &lowerRange, &upperRange);
667}
668
669/*
670The sorting problem happens when the inital tangents are not a true indicator of the curve direction
671Nearly always, the initial tangents do give the right answer,
672so the trick is to figure out when the initial tangent cannot be trusted.
673If the convex hulls of both curves are in the same half plane, and not overlapping, sorting the
674hulls is enough.
675If the hulls overlap, and have the same general direction, then intersect the shorter end point ray
676with the opposing curve, and see on which side of the shorter curve the opposing intersection lies.
677Otherwise, if the control vector is extremely short, likely the point on curve must be computed
678If moving the control point slightly can change the sign of the cross product, either answer could
679be "right".
680We need to determine how short is extremely short. Move the control point a set percentage of
681the largest length to determine how stable the curve is vis-a-vis the initial tangent.
682*/
683
caryclarka35ab3e2016-10-20 08:32:18 -0700684static const QuadPts extremeTests[][2] = {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000685 {
686 {{{-708.0077926931004,-154.61669472244046},
687 {-707.9234268635319,-154.30459999551294},
688 {505.58447265625,-504.9130859375}}},
689 {{{-708.0077926931004,-154.61669472244046},
690 {-711.127526325141,-163.9446090624656},
691 {-32.39227294921875,-906.3277587890625}}},
692 }, {
693 {{{-708.0077926931004,-154.61669472244046},
694 {-708.2875337527566,-154.36676458635623},
695 {505.58447265625,-504.9130859375}}},
696 {{{-708.0077926931004,-154.61669472244046},
697 {-708.4111557216864,-154.5366642875255},
698 {-32.39227294921875,-906.3277587890625}}},
699 }, {
700 {{{-609.0230951752058,-267.5435593490574},
701 {-594.1120809906336,-136.08492475411555},
702 {505.58447265625,-504.9130859375}}},
703 {{{-609.0230951752058,-267.5435593490574},
704 {-693.7467719138988,-341.3259237831895},
705 {-32.39227294921875,-906.3277587890625}}}
706 }, {
707 {{{-708.0077926931004,-154.61669472244046},
708 {-707.9994640658723,-154.58588461064852},
709 {505.58447265625,-504.9130859375}}},
710 {{{-708.0077926931004,-154.61669472244046},
711 {-708.0239418990758,-154.6403553507124},
712 {-32.39227294921875,-906.3277587890625}}}
713 }, {
714 {{{-708.0077926931004,-154.61669472244046},
715 {-707.9993222215099,-154.55999389855003},
716 {68.88981098017803,296.9273945411635}}},
717 {{{-708.0077926931004,-154.61669472244046},
718 {-708.0509091919608,-154.64675214697067},
719 {-777.4871194247767,-995.1470120113145}}}
720 }, {
721 {{{-708.0077926931004,-154.61669472244046},
722 {-708.0060491116379,-154.60889321524968},
723 {229.97088707895057,-430.0569357467175}}},
724 {{{-708.0077926931004,-154.61669472244046},
725 {-708.013911296257,-154.6219143988058},
726 {138.13162892614037,-573.3689311737394}}}
727 }, {
728 {{{-543.2570545751013,-237.29243831075053},
729 {-452.4119186056987,-143.47223056267802},
730 {229.97088707895057,-430.0569357467175}}},
731 {{{-543.2570545751013,-237.29243831075053},
732 {-660.5330371214436,-362.0016148388},
733 {138.13162892614037,-573.3689311737394}}},
734 },
735};
736
737static double endCtrlRatio(const SkDQuad quad) {
738 SkDVector longEdge = quad[2] - quad[0];
739 double longLen = longEdge.length();
740 SkDVector shortEdge = quad[1] - quad[0];
741 double shortLen = shortEdge.length();
742 return longLen / shortLen;
743}
744
745static void computeMV(const SkDQuad& quad, const SkDVector& v, double m, SkDVector mV[2]) {
746 SkDPoint mPta = {quad[1].fX - m * v.fY, quad[1].fY + m * v.fX};
747 SkDPoint mPtb = {quad[1].fX + m * v.fY, quad[1].fY - m * v.fX};
748 mV[0] = mPta - quad[0];
749 mV[1] = mPtb - quad[0];
750}
751
752static double mDistance(skiatest::Reporter* reporter, bool agrees, const SkDQuad& q1,
753 const SkDQuad& q2) {
754 if (1 && agrees) {
755 return SK_ScalarMax;
756 }
757 // how close is the angle from inflecting in the opposite direction?
758 SkDVector v1 = q1[1] - q1[0];
759 SkDVector v2 = q2[1] - q2[0];
760 double dir = v1.crossCheck(v2);
761 REPORTER_ASSERT(reporter, dir != 0);
762 // solve for opposite direction displacement scale factor == m
763 // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x
764 // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1]
765 // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x)
766 // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x)
767 // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x
768 // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y)
769 // m = v1.cross(v2) / v1.dot(v2)
770 double div = v1.dot(v2);
771 REPORTER_ASSERT(reporter, div != 0);
772 double m = dir / div;
773 SkDVector mV1[2], mV2[2];
774 computeMV(q1, v1, m, mV1);
775 computeMV(q2, v2, m, mV2);
776 double dist1 = v1.length() * m;
777 double dist2 = v2.length() * m;
778 if (gPathOpsAngleIdeasVerbose) {
779 SkDebugf("%c r1=%1.9g r2=%1.9g m=%1.9g dist1=%1.9g dist2=%1.9g "
780 " dir%c 1a=%1.9g 1b=%1.9g 2a=%1.9g 2b=%1.9g\n", agrees ? 'T' : 'F',
781 endCtrlRatio(q1), endCtrlRatio(q2), m, dist1, dist2, dir > 0 ? '+' : '-',
782 mV1[0].crossCheck(v2), mV1[1].crossCheck(v2),
783 mV2[0].crossCheck(v1), mV2[1].crossCheck(v1));
784 }
785 if (1) {
786 bool use1 = fabs(dist1) < fabs(dist2);
787 if (gPathOpsAngleIdeasVerbose) {
788 SkDebugf("%c dist=%1.9g r=%1.9g\n", agrees ? 'T' : 'F', use1 ? dist1 : dist2,
789 use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
790 }
791 return fabs(use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
792 }
793 return SK_ScalarMax;
794}
795
796static void midPointAgrees(skiatest::Reporter* reporter, const SkDQuad& q1, const SkDQuad& q2,
797 bool ccw) {
798 SkDPoint mid1 = q1.ptAtT(0.5);
799 SkDVector m1 = mid1 - q1[0];
800 SkDPoint mid2 = q2.ptAtT(0.5);
801 SkDVector m2 = mid2 - q2[0];
802 REPORTER_ASSERT(reporter, ccw ? m1.crossCheck(m2) < 0 : m1.crossCheck(m2) > 0);
803}
804
805DEF_TEST(PathOpsAngleExtreme, reporter) {
806 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
807 return;
808 }
809 double maxR = SK_ScalarMax;
810 for (int index = 0; index < (int) SK_ARRAY_COUNT(extremeTests); ++index) {
caryclarka35ab3e2016-10-20 08:32:18 -0700811 const QuadPts& qu1 = extremeTests[index][0];
812 const QuadPts& qu2 = extremeTests[index][1];
813 SkDQuad quad1, quad2;
814 quad1.debugSet(qu1.fPts);
815 quad2.debugSet(qu2.fPts);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000816 if (gPathOpsAngleIdeasVerbose) {
817 SkDebugf("%s %d\n", __FUNCTION__, index);
818 }
819 REPORTER_ASSERT(reporter, quad1[0] == quad2[0]);
820 SkIntersections i;
821 i.intersect(quad1, quad2);
822 REPORTER_ASSERT(reporter, i.used() == 1);
823 REPORTER_ASSERT(reporter, i.pt(0) == quad1[0]);
824 int overlap = quadHullsOverlap(reporter, quad1, quad2);
825 REPORTER_ASSERT(reporter, overlap >= 0);
826 SkDVector sweep[2], tweep[2];
827 setQuadHullSweep(quad1, sweep);
828 setQuadHullSweep(quad2, tweep);
829 double s0xt0 = sweep[0].crossCheck(tweep[0]);
830 REPORTER_ASSERT(reporter, s0xt0 != 0);
831 bool ccw = s0xt0 < 0;
832 bool agrees = bruteForceCheck(reporter, quad1, quad2, ccw);
Brian Osman788b9162020-02-07 10:36:46 -0500833 maxR = std::min(maxR, mDistance(reporter, agrees, quad1, quad2));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000834 if (agrees) {
835 continue;
836 }
837 midPointAgrees(reporter, quad1, quad2, !ccw);
838 SkDQuad q1 = quad1;
839 SkDQuad q2 = quad2;
840 double loFail = 1;
841 double hiPass = 2;
842 // double vectors until t passes
843 do {
844 q1[1].fX = quad1[0].fX * (1 - hiPass) + quad1[1].fX * hiPass;
845 q1[1].fY = quad1[0].fY * (1 - hiPass) + quad1[1].fY * hiPass;
846 q2[1].fX = quad2[0].fX * (1 - hiPass) + quad2[1].fX * hiPass;
847 q2[1].fY = quad2[0].fY * (1 - hiPass) + quad2[1].fY * hiPass;
848 agrees = bruteForceCheck(reporter, q1, q2, ccw);
Brian Osman788b9162020-02-07 10:36:46 -0500849 maxR = std::min(maxR, mDistance(reporter, agrees, q1, q2));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000850 if (agrees) {
851 break;
852 }
853 midPointAgrees(reporter, quad1, quad2, !ccw);
854 loFail = hiPass;
855 hiPass *= 2;
856 } while (true);
857 // binary search to find minimum pass
858 double midTest = (loFail + hiPass) / 2;
859 double step = (hiPass - loFail) / 4;
860 while (step > FLT_EPSILON) {
861 q1[1].fX = quad1[0].fX * (1 - midTest) + quad1[1].fX * midTest;
862 q1[1].fY = quad1[0].fY * (1 - midTest) + quad1[1].fY * midTest;
863 q2[1].fX = quad2[0].fX * (1 - midTest) + quad2[1].fX * midTest;
864 q2[1].fY = quad2[0].fY * (1 - midTest) + quad2[1].fY * midTest;
865 agrees = bruteForceCheck(reporter, q1, q2, ccw);
Brian Osman788b9162020-02-07 10:36:46 -0500866 maxR = std::min(maxR, mDistance(reporter, agrees, q1, q2));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000867 if (!agrees) {
868 midPointAgrees(reporter, quad1, quad2, !ccw);
869 }
870 midTest += agrees ? -step : step;
871 step /= 2;
872 }
873#ifdef SK_DEBUG
874// DumpQ(q1, q2, 999);
875#endif
876 }
877 if (gPathOpsAngleIdeasVerbose) {
878 SkDebugf("maxR=%1.9g\n", maxR);
879 }
880}