blob: 9d1b5994b2da91dde992ee0ed23108ed9d7a3783 [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 */
7#include "PathOpsTestCommon.h"
8#include "SkIntersections.h"
caryclark54359292015-03-26 07:52:43 -07009#include "SkOpContour.h"
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000010#include "SkOpSegment.h"
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000011#include "SkRandom.h"
12#include "SkTArray.h"
13#include "SkTSort.h"
14#include "Test.h"
15
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
80 const SkDQuad circle[8] = {{{{ r, 0}, { r, -s}, { m, -m}}},
81 {{{ 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) {
89 double t = testArc(reporter, quad, circle[octant], octant);
90 if (t < 0) {
91 continue;
92 }
93 for (int index = 0; index < tArray->count(); ++index) {
94 double matchT = (*tArray)[index];
95 if (approximately_equal(t, matchT)) {
96 goto next;
97 }
98 }
99 tArray->push_back(t);
100next: ;
101 }
102}
103
104static double quadAngle(skiatest::Reporter* reporter, const SkDQuad& quad, double t) {
105 const SkDVector& pt = quad.ptAtT(t) - quad[0];
106 double angle = (atan2(pt.fY, pt.fX) + SK_ScalarPI) * 8 / (SK_ScalarPI * 2);
107 REPORTER_ASSERT(reporter, angle >= 0 && angle <= 8);
108 return angle;
109}
110
111static bool angleDirection(double a1, double a2) {
112 double delta = a1 - a2;
113 return (delta < 4 && delta > 0) || delta < -4;
114}
115
116static void setQuadHullSweep(const SkDQuad& quad, SkDVector sweep[2]) {
117 sweep[0] = quad[1] - quad[0];
118 sweep[1] = quad[2] - quad[0];
119}
120
121static double distEndRatio(double dist, const SkDQuad& quad) {
122 SkDVector v[] = {quad[2] - quad[0], quad[1] - quad[0], quad[2] - quad[1]};
123 double longest = SkTMax(v[0].length(), SkTMax(v[1].length(), v[2].length()));
124 return longest / dist;
125}
126
127static bool checkParallel(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2) {
128 SkDVector sweep[2], tweep[2];
129 setQuadHullSweep(quad1, sweep);
130 setQuadHullSweep(quad2, tweep);
131 // if the ctrl tangents are not nearly parallel, use them
132 // solve for opposite direction displacement scale factor == m
133 // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x
134 // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1]
135 // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x)
136 // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x)
137 // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x
138 // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y)
139 // m = v1.cross(v2) / v1.dot(v2)
140 double s0dt0 = sweep[0].dot(tweep[0]);
141 REPORTER_ASSERT(reporter, s0dt0 != 0);
142 double s0xt0 = sweep[0].crossCheck(tweep[0]);
143 double m = s0xt0 / s0dt0;
144 double sDist = sweep[0].length() * m;
145 double tDist = tweep[0].length() * m;
146 bool useS = fabs(sDist) < fabs(tDist);
147 double mFactor = fabs(useS ? distEndRatio(sDist, quad1) : distEndRatio(tDist, quad2));
148 if (mFactor < 5000) { // empirically found limit
149 return s0xt0 < 0;
150 }
151 SkDVector m0 = quad1.ptAtT(0.5) - quad1[0];
152 SkDVector m1 = quad2.ptAtT(0.5) - quad2[0];
153 return m0.crossCheck(m1) < 0;
154}
155
156/* returns
157 -1 if overlaps
158 0 if no overlap cw
159 1 if no overlap ccw
160*/
161static int quadHullsOverlap(skiatest::Reporter* reporter, const SkDQuad& quad1,
162 const SkDQuad& quad2) {
163 SkDVector sweep[2], tweep[2];
164 setQuadHullSweep(quad1, sweep);
165 setQuadHullSweep(quad2, tweep);
166 double s0xs1 = sweep[0].crossCheck(sweep[1]);
167 double s0xt0 = sweep[0].crossCheck(tweep[0]);
168 double s1xt0 = sweep[1].crossCheck(tweep[0]);
169 bool tBetweenS = s0xs1 > 0 ? s0xt0 > 0 && s1xt0 < 0 : s0xt0 < 0 && s1xt0 > 0;
170 double s0xt1 = sweep[0].crossCheck(tweep[1]);
171 double s1xt1 = sweep[1].crossCheck(tweep[1]);
172 tBetweenS |= s0xs1 > 0 ? s0xt1 > 0 && s1xt1 < 0 : s0xt1 < 0 && s1xt1 > 0;
173 double t0xt1 = tweep[0].crossCheck(tweep[1]);
174 if (tBetweenS) {
175 return -1;
176 }
177 if ((s0xt0 == 0 && s1xt1 == 0) || (s1xt0 == 0 && s0xt1 == 0)) { // s0 to s1 equals t0 to t1
178 return -1;
179 }
180 bool sBetweenT = t0xt1 > 0 ? s0xt0 < 0 && s0xt1 > 0 : s0xt0 > 0 && s0xt1 < 0;
181 sBetweenT |= t0xt1 > 0 ? s1xt0 < 0 && s1xt1 > 0 : s1xt0 > 0 && s1xt1 < 0;
182 if (sBetweenT) {
183 return -1;
184 }
185 // if all of the sweeps are in the same half plane, then the order of any pair is enough
186 if (s0xt0 >= 0 && s0xt1 >= 0 && s1xt0 >= 0 && s1xt1 >= 0) {
187 return 0;
188 }
189 if (s0xt0 <= 0 && s0xt1 <= 0 && s1xt0 <= 0 && s1xt1 <= 0) {
190 return 1;
191 }
192 // if the outside sweeps are greater than 180 degress:
193 // first assume the inital tangents are the ordering
194 // if the midpoint direction matches the inital order, that is enough
195 SkDVector m0 = quad1.ptAtT(0.5) - quad1[0];
196 SkDVector m1 = quad2.ptAtT(0.5) - quad2[0];
197 double m0xm1 = m0.crossCheck(m1);
198 if (s0xt0 > 0 && m0xm1 > 0) {
199 return 0;
200 }
201 if (s0xt0 < 0 && m0xm1 < 0) {
202 return 1;
203 }
204 REPORTER_ASSERT(reporter, s0xt0 != 0);
205 return checkParallel(reporter, quad1, quad2);
206}
207
208static double radianSweep(double start, double end) {
209 double sweep = end - start;
210 if (sweep > SK_ScalarPI) {
211 sweep -= 2 * SK_ScalarPI;
212 } else if (sweep < -SK_ScalarPI) {
213 sweep += 2 * SK_ScalarPI;
214 }
215 return sweep;
216}
217
218static bool radianBetween(double start, double test, double end) {
219 double startToEnd = radianSweep(start, end);
220 double startToTest = radianSweep(start, test);
221 double testToEnd = radianSweep(test, end);
222 return (startToTest <= 0 && testToEnd <= 0 && startToTest >= startToEnd) ||
223 (startToTest >= 0 && testToEnd >= 0 && startToTest <= startToEnd);
224}
225
226static bool orderTRange(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
227 double r, TRange* result) {
228 SkTArray<double, false> t1Array, t2Array;
229 orderQuads(reporter, quad1, r, &t1Array);
230 orderQuads(reporter,quad2, r, &t2Array);
231 if (!t1Array.count() || !t2Array.count()) {
232 return false;
233 }
234 SkTQSort<double>(t1Array.begin(), t1Array.end() - 1);
235 SkTQSort<double>(t2Array.begin(), t2Array.end() - 1);
236 double t1 = result->tMin1 = t1Array[0];
237 double t2 = result->tMin2 = t2Array[0];
238 double a1 = quadAngle(reporter,quad1, t1);
239 double a2 = quadAngle(reporter,quad2, t2);
240 if (approximately_equal(a1, a2)) {
241 return false;
242 }
243 bool refCCW = angleDirection(a1, a2);
244 result->t1 = t1;
245 result->t2 = t2;
246 result->tMin = SkTMin(t1, t2);
247 result->a1 = a1;
248 result->a2 = a2;
249 result->ccw = refCCW;
250 return true;
251}
252
253static bool equalPoints(const SkDPoint& pt1, const SkDPoint& pt2, double max) {
254 return approximately_zero_when_compared_to(pt1.fX - pt2.fX, max)
255 && approximately_zero_when_compared_to(pt1.fY - pt2.fY, max);
256}
257
258static double maxDist(const SkDQuad& quad) {
259 SkDRect bounds;
260 bounds.setBounds(quad);
261 SkDVector corner[4] = {
262 { bounds.fLeft - quad[0].fX, bounds.fTop - quad[0].fY },
263 { bounds.fRight - quad[0].fX, bounds.fTop - quad[0].fY },
264 { bounds.fLeft - quad[0].fX, bounds.fBottom - quad[0].fY },
265 { bounds.fRight - quad[0].fX, bounds.fBottom - quad[0].fY }
266 };
267 double max = 0;
268 for (unsigned index = 0; index < SK_ARRAY_COUNT(corner); ++index) {
269 max = SkTMax(max, corner[index].length());
270 }
271 return max;
272}
273
274static double maxQuad(const SkDQuad& quad) {
275 double max = 0;
276 for (int index = 0; index < 2; ++index) {
277 max = SkTMax(max, fabs(quad[index].fX));
278 max = SkTMax(max, fabs(quad[index].fY));
279 }
280 return max;
281}
282
283static bool bruteMinT(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
284 TRange* lowerRange, TRange* upperRange) {
285 double maxRadius = SkTMin(maxDist(quad1), maxDist(quad2));
286 double maxQuads = SkTMax(maxQuad(quad1), maxQuad(quad2));
287 double r = maxRadius / 2;
288 double rStep = r / 2;
289 SkDPoint best1 = {SK_ScalarInfinity, SK_ScalarInfinity};
290 SkDPoint best2 = {SK_ScalarInfinity, SK_ScalarInfinity};
291 int bestCCW = -1;
292 double bestR = maxRadius;
293 upperRange->tMin = 0;
294 lowerRange->tMin = 1;
295 do {
296 do { // find upper bounds of single result
297 TRange tRange;
298 bool stepUp = orderTRange(reporter, quad1, quad2, r, &tRange);
299 if (stepUp) {
300 SkDPoint pt1 = quad1.ptAtT(tRange.t1);
301 if (equalPoints(pt1, best1, maxQuads)) {
302 break;
303 }
304 best1 = pt1;
305 SkDPoint pt2 = quad2.ptAtT(tRange.t2);
306 if (equalPoints(pt2, best2, maxQuads)) {
307 break;
308 }
309 best2 = pt2;
310 if (gPathOpsAngleIdeasVerbose) {
311 SkDebugf("u bestCCW=%d ccw=%d bestMin=%1.9g:%1.9g r=%1.9g tMin=%1.9g\n",
312 bestCCW, tRange.ccw, lowerRange->tMin, upperRange->tMin, r,
313 tRange.tMin);
314 }
315 if (bestCCW >= 0 && bestCCW != (int) tRange.ccw) {
316 if (tRange.tMin < upperRange->tMin) {
317 upperRange->tMin = 0;
318 } else {
319 stepUp = false;
320 }
321 }
322 if (upperRange->tMin < tRange.tMin) {
323 bestCCW = tRange.ccw;
324 bestR = r;
325 *upperRange = tRange;
326 }
327 if (lowerRange->tMin > tRange.tMin) {
328 *lowerRange = tRange;
329 }
330 }
331 r += stepUp ? rStep : -rStep;
332 rStep /= 2;
333 } while (rStep > FLT_EPSILON);
334 if (bestCCW < 0) {
335 REPORTER_ASSERT(reporter, bestR < maxRadius);
336 return false;
337 }
338 double lastHighR = bestR;
339 r = bestR / 2;
340 rStep = r / 2;
341 do { // find lower bounds of single result
342 TRange tRange;
343 bool success = orderTRange(reporter, quad1, quad2, r, &tRange);
344 if (success) {
345 if (gPathOpsAngleIdeasVerbose) {
346 SkDebugf("l bestCCW=%d ccw=%d bestMin=%1.9g:%1.9g r=%1.9g tMin=%1.9g\n",
347 bestCCW, tRange.ccw, lowerRange->tMin, upperRange->tMin, r,
348 tRange.tMin);
349 }
350 if (bestCCW != (int) tRange.ccw || upperRange->tMin < tRange.tMin) {
351 bestCCW = tRange.ccw;
352 *upperRange = tRange;
353 bestR = lastHighR;
354 break; // need to establish a new upper bounds
355 }
356 SkDPoint pt1 = quad1.ptAtT(tRange.t1);
357 SkDPoint pt2 = quad2.ptAtT(tRange.t2);
358 if (equalPoints(pt1, best1, maxQuads)) {
359 goto breakOut;
360 }
361 best1 = pt1;
362 if (equalPoints(pt2, best2, maxQuads)) {
363 goto breakOut;
364 }
365 best2 = pt2;
366 if (equalPoints(pt1, pt2, maxQuads)) {
367 success = false;
368 } else {
369 if (upperRange->tMin < tRange.tMin) {
370 *upperRange = tRange;
371 }
372 if (lowerRange->tMin > tRange.tMin) {
373 *lowerRange = tRange;
374 }
375 }
376 lastHighR = SkTMin(r, lastHighR);
377 }
378 r += success ? -rStep : rStep;
379 rStep /= 2;
380 } while (rStep > FLT_EPSILON);
381 } while (rStep > FLT_EPSILON);
382breakOut:
383 if (gPathOpsAngleIdeasVerbose) {
384 SkDebugf("l a2-a1==%1.9g\n", lowerRange->a2 - lowerRange->a1);
385 }
386 return true;
387}
388
389static void bruteForce(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
390 bool ccw) {
391 if (!gPathOpsAngleIdeasEnableBruteCheck) {
392 return;
393 }
394 TRange lowerRange, upperRange;
395 bool result = bruteMinT(reporter, quad1, quad2, &lowerRange, &upperRange);
396 REPORTER_ASSERT(reporter, result);
397 double angle = fabs(lowerRange.a2 - lowerRange.a1);
398 REPORTER_ASSERT(reporter, angle > 3.998 || ccw == upperRange.ccw);
399}
400
401static bool bruteForceCheck(skiatest::Reporter* reporter, const SkDQuad& quad1,
402 const SkDQuad& quad2, bool ccw) {
403 TRange lowerRange, upperRange;
404 bool result = bruteMinT(reporter, quad1, quad2, &lowerRange, &upperRange);
405 REPORTER_ASSERT(reporter, result);
406 return ccw == upperRange.ccw;
407}
408
caryclark55888e42016-07-18 10:01:36 -0700409static void makeSegment(SkOpContour* contour, const SkDQuad& quad, SkPoint shortQuad[3]) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000410 shortQuad[0] = quad[0].asSkPoint();
411 shortQuad[1] = quad[1].asSkPoint();
412 shortQuad[2] = quad[2].asSkPoint();
caryclark55888e42016-07-18 10:01:36 -0700413 contour->addQuad(shortQuad);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000414}
415
416static void testQuadAngles(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
caryclark54359292015-03-26 07:52:43 -0700417 int testNo, SkChunkAlloc* allocator) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000418 SkPoint shortQuads[2][3];
caryclark54359292015-03-26 07:52:43 -0700419
caryclark624637c2015-05-11 07:21:27 -0700420 SkOpContourHead contour;
caryclark55888e42016-07-18 10:01:36 -0700421 SkOpGlobalState state(&contour, allocator SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr));
caryclark54359292015-03-26 07:52:43 -0700422 contour.init(&state, false, false);
caryclark55888e42016-07-18 10:01:36 -0700423 makeSegment(&contour, quad1, shortQuads[0]);
424 makeSegment(&contour, quad1, shortQuads[1]);
caryclark54359292015-03-26 07:52:43 -0700425 SkOpSegment* seg1 = contour.first();
caryclark55888e42016-07-18 10:01:36 -0700426 seg1->debugAddAngle(0, 1);
caryclark54359292015-03-26 07:52:43 -0700427 SkOpSegment* seg2 = seg1->next();
caryclark55888e42016-07-18 10:01:36 -0700428 seg2->debugAddAngle(0, 1);
caryclark54359292015-03-26 07:52:43 -0700429 int realOverlap = PathOpsAngleTester::ConvexHullOverlaps(*seg1->debugLastAngle(),
430 *seg2->debugLastAngle());
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000431 const SkDPoint& origin = quad1[0];
432 REPORTER_ASSERT(reporter, origin == quad2[0]);
433 double a1s = atan2(origin.fY - quad1[1].fY, quad1[1].fX - origin.fX);
434 double a1e = atan2(origin.fY - quad1[2].fY, quad1[2].fX - origin.fX);
435 double a2s = atan2(origin.fY - quad2[1].fY, quad2[1].fX - origin.fX);
436 double a2e = atan2(origin.fY - quad2[2].fY, quad2[2].fX - origin.fX);
437 bool oldSchoolOverlap = radianBetween(a1s, a2s, a1e)
438 || radianBetween(a1s, a2e, a1e) || radianBetween(a2s, a1s, a2e)
439 || radianBetween(a2s, a1e, a2e);
440 int overlap = quadHullsOverlap(reporter, quad1, quad2);
441 bool realMatchesOverlap = realOverlap == overlap || SK_ScalarPI - fabs(a2s - a1s) < 0.002;
442 if (realOverlap != overlap) {
443 SkDebugf("\nSK_ScalarPI - fabs(a2s - a1s) = %1.9g\n", SK_ScalarPI - fabs(a2s - a1s));
444 }
445 if (!realMatchesOverlap) {
446 DumpQ(quad1, quad2, testNo);
447 }
448 REPORTER_ASSERT(reporter, realMatchesOverlap);
449 if (oldSchoolOverlap != (overlap < 0)) {
450 overlap = quadHullsOverlap(reporter, quad1, quad2); // set a breakpoint and debug if assert fires
451 REPORTER_ASSERT(reporter, oldSchoolOverlap == (overlap < 0));
452 }
453 SkDVector v1s = quad1[1] - quad1[0];
454 SkDVector v1e = quad1[2] - quad1[0];
455 SkDVector v2s = quad2[1] - quad2[0];
456 SkDVector v2e = quad2[2] - quad2[0];
457 double vDir[2] = { v1s.cross(v1e), v2s.cross(v2e) };
458 bool ray1In2 = v1s.cross(v2s) * vDir[1] <= 0 && v1s.cross(v2e) * vDir[1] >= 0;
459 bool ray2In1 = v2s.cross(v1s) * vDir[0] <= 0 && v2s.cross(v1e) * vDir[0] >= 0;
460 if (overlap >= 0) {
461 // verify that hulls really don't overlap
462 REPORTER_ASSERT(reporter, !ray1In2);
463 REPORTER_ASSERT(reporter, !ray2In1);
464 bool ctrl1In2 = v1e.cross(v2s) * vDir[1] <= 0 && v1e.cross(v2e) * vDir[1] >= 0;
465 REPORTER_ASSERT(reporter, !ctrl1In2);
466 bool ctrl2In1 = v2e.cross(v1s) * vDir[0] <= 0 && v2e.cross(v1e) * vDir[0] >= 0;
467 REPORTER_ASSERT(reporter, !ctrl2In1);
468 // check answer against reference
469 bruteForce(reporter, quad1, quad2, overlap > 0);
470 }
471 // continue end point rays and see if they intersect the opposite curve
472 SkDLine rays[] = {{{origin, quad2[2]}}, {{origin, quad1[2]}}};
473 const SkDQuad* quads[] = {&quad1, &quad2};
474 SkDVector midSpokes[2];
475 SkIntersections intersect[2];
476 double minX, minY, maxX, maxY;
477 minX = minY = SK_ScalarInfinity;
478 maxX = maxY = -SK_ScalarInfinity;
479 double maxWidth = 0;
480 bool useIntersect = false;
481 double smallestTs[] = {1, 1};
482 for (unsigned index = 0; index < SK_ARRAY_COUNT(quads); ++index) {
483 const SkDQuad& q = *quads[index];
484 midSpokes[index] = q.ptAtT(0.5) - origin;
485 minX = SkTMin(SkTMin(SkTMin(minX, origin.fX), q[1].fX), q[2].fX);
486 minY = SkTMin(SkTMin(SkTMin(minY, origin.fY), q[1].fY), q[2].fY);
487 maxX = SkTMax(SkTMax(SkTMax(maxX, origin.fX), q[1].fX), q[2].fX);
488 maxY = SkTMax(SkTMax(SkTMax(maxY, origin.fY), q[1].fY), q[2].fY);
489 maxWidth = SkTMax(maxWidth, SkTMax(maxX - minX, maxY - minY));
490 intersect[index].intersectRay(q, rays[index]);
491 const SkIntersections& i = intersect[index];
492 REPORTER_ASSERT(reporter, i.used() >= 1);
493 bool foundZero = false;
494 double smallT = 1;
495 for (int idx2 = 0; idx2 < i.used(); ++idx2) {
496 double t = i[0][idx2];
497 if (t == 0) {
498 foundZero = true;
499 continue;
500 }
501 if (smallT > t) {
502 smallT = t;
503 }
504 }
505 REPORTER_ASSERT(reporter, foundZero == true);
506 if (smallT == 1) {
507 continue;
508 }
509 SkDVector ray = q.ptAtT(smallT) - origin;
510 SkDVector end = rays[index][1] - origin;
511 if (ray.fX * end.fX < 0 || ray.fY * end.fY < 0) {
512 continue;
513 }
514 double rayDist = ray.length();
515 double endDist = end.length();
516 double delta = fabs(rayDist - endDist) / maxWidth;
517 if (delta > 1e-4) {
518 useIntersect ^= true;
519 }
520 smallestTs[index] = smallT;
521 }
522 bool firstInside;
523 if (useIntersect) {
524 int sIndex = (int) (smallestTs[1] < 1);
525 REPORTER_ASSERT(reporter, smallestTs[sIndex ^ 1] == 1);
526 double t = smallestTs[sIndex];
527 const SkDQuad& q = *quads[sIndex];
528 SkDVector ray = q.ptAtT(t) - origin;
529 SkDVector end = rays[sIndex][1] - origin;
530 double rayDist = ray.length();
531 double endDist = end.length();
532 SkDVector mid = q.ptAtT(t / 2) - origin;
533 double midXray = mid.crossCheck(ray);
534 if (gPathOpsAngleIdeasVerbose) {
535 SkDebugf("rayDist>endDist:%d sIndex==0:%d vDir[sIndex]<0:%d midXray<0:%d\n",
536 rayDist > endDist, sIndex == 0, vDir[sIndex] < 0, midXray < 0);
537 }
538 SkASSERT(SkScalarSignAsInt(SkDoubleToScalar(midXray))
539 == SkScalarSignAsInt(SkDoubleToScalar(vDir[sIndex])));
540 firstInside = (rayDist > endDist) ^ (sIndex == 0) ^ (vDir[sIndex] < 0);
541 } else if (overlap >= 0) {
542 return; // answer has already been determined
543 } else {
544 firstInside = checkParallel(reporter, quad1, quad2);
545 }
546 if (overlap < 0) {
547 SkDEBUGCODE(int realEnds =)
caryclark54359292015-03-26 07:52:43 -0700548 PathOpsAngleTester::EndsIntersect(*seg1->debugLastAngle(),
549 *seg2->debugLastAngle());
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000550 SkASSERT(realEnds == (firstInside ? 1 : 0));
551 }
552 bruteForce(reporter, quad1, quad2, firstInside);
553}
554
555DEF_TEST(PathOpsAngleOverlapHullsOne, reporter) {
caryclark54359292015-03-26 07:52:43 -0700556 SkChunkAlloc allocator(4096);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000557// gPathOpsAngleIdeasVerbose = true;
558 const SkDQuad quads[] = {
559{{{939.4808349609375, 914.355224609375}, {-357.7921142578125, 590.842529296875}, {736.8936767578125, -350.717529296875}}},
560{{{939.4808349609375, 914.355224609375}, {-182.85418701171875, 634.4552001953125}, {-509.62615966796875, 576.1182861328125}}}
561 };
562 for (int index = 0; index < (int) SK_ARRAY_COUNT(quads); index += 2) {
caryclark54359292015-03-26 07:52:43 -0700563 testQuadAngles(reporter, quads[index], quads[index + 1], 0, &allocator);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000564 }
565}
566
567DEF_TEST(PathOpsAngleOverlapHulls, reporter) {
caryclark54359292015-03-26 07:52:43 -0700568 SkChunkAlloc allocator(4096);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000569 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
570 return;
571 }
572 SkRandom ran;
573 for (int index = 0; index < 100000; ++index) {
574 if (index % 1000 == 999) SkDebugf(".");
575 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
576 SkDQuad quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
577 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
578 if (quad1[0] == quad1[2]) {
579 continue;
580 }
581 SkDQuad quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
582 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
583 if (quad2[0] == quad2[2]) {
584 continue;
585 }
586 SkIntersections i;
587 i.intersect(quad1, quad2);
588 REPORTER_ASSERT(reporter, i.used() >= 1);
589 if (i.used() > 1) {
590 continue;
591 }
caryclark54359292015-03-26 07:52:43 -0700592 testQuadAngles(reporter, quad1, quad2, index, &allocator);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000593 }
594}
595
596DEF_TEST(PathOpsAngleBruteT, reporter) {
597 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
598 return;
599 }
600 SkRandom ran;
601 double smaller = SK_Scalar1;
602 SkDQuad small[2];
603 SkDEBUGCODE(int smallIndex);
604 for (int index = 0; index < 100000; ++index) {
605 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
606 SkDQuad quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
607 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
608 if (quad1[0] == quad1[2]) {
609 continue;
610 }
611 SkDQuad quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
612 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
613 if (quad2[0] == quad2[2]) {
614 continue;
615 }
616 SkIntersections i;
617 i.intersect(quad1, quad2);
618 REPORTER_ASSERT(reporter, i.used() >= 1);
619 if (i.used() > 1) {
620 continue;
621 }
622 TRange lowerRange, upperRange;
623 bool result = bruteMinT(reporter, quad1, quad2, &lowerRange, &upperRange);
624 REPORTER_ASSERT(reporter, result);
625 double min = SkTMin(upperRange.t1, upperRange.t2);
626 if (smaller > min) {
627 small[0] = quad1;
628 small[1] = quad2;
629 SkDEBUGCODE(smallIndex = index);
630 smaller = min;
631 }
632 }
633#ifdef SK_DEBUG
634 DumpQ(small[0], small[1], smallIndex);
635#endif
636}
637
638DEF_TEST(PathOpsAngleBruteTOne, reporter) {
639// gPathOpsAngleIdeasVerbose = true;
640 const SkDQuad quads[] = {
641{{{-770.8492431640625, 948.2369384765625}, {-853.37066650390625, 972.0301513671875}, {-200.62042236328125, -26.7174072265625}}},
642{{{-770.8492431640625, 948.2369384765625}, {513.602783203125, 578.8681640625}, {960.641357421875, -813.69757080078125}}},
643{{{563.8267822265625, -107.4566650390625}, {-44.67724609375, -136.57452392578125}, {492.3856201171875, -268.79644775390625}}},
644{{{563.8267822265625, -107.4566650390625}, {708.049072265625, -100.77789306640625}, {-48.88226318359375, 967.9022216796875}}},
645{{{598.857421875, 846.345458984375}, {-644.095703125, -316.12921142578125}, {-97.64599609375, 20.6158447265625}}},
646{{{598.857421875, 846.345458984375}, {715.7142333984375, 955.3599853515625}, {-919.9478759765625, 691.611328125}}},
647 };
648 TRange lowerRange, upperRange;
649 bruteMinT(reporter, quads[0], quads[1], &lowerRange, &upperRange);
650 bruteMinT(reporter, quads[2], quads[3], &lowerRange, &upperRange);
651 bruteMinT(reporter, quads[4], quads[5], &lowerRange, &upperRange);
652}
653
654/*
655The sorting problem happens when the inital tangents are not a true indicator of the curve direction
656Nearly always, the initial tangents do give the right answer,
657so the trick is to figure out when the initial tangent cannot be trusted.
658If the convex hulls of both curves are in the same half plane, and not overlapping, sorting the
659hulls is enough.
660If the hulls overlap, and have the same general direction, then intersect the shorter end point ray
661with the opposing curve, and see on which side of the shorter curve the opposing intersection lies.
662Otherwise, if the control vector is extremely short, likely the point on curve must be computed
663If moving the control point slightly can change the sign of the cross product, either answer could
664be "right".
665We need to determine how short is extremely short. Move the control point a set percentage of
666the largest length to determine how stable the curve is vis-a-vis the initial tangent.
667*/
668
669static const SkDQuad extremeTests[][2] = {
670 {
671 {{{-708.0077926931004,-154.61669472244046},
672 {-707.9234268635319,-154.30459999551294},
673 {505.58447265625,-504.9130859375}}},
674 {{{-708.0077926931004,-154.61669472244046},
675 {-711.127526325141,-163.9446090624656},
676 {-32.39227294921875,-906.3277587890625}}},
677 }, {
678 {{{-708.0077926931004,-154.61669472244046},
679 {-708.2875337527566,-154.36676458635623},
680 {505.58447265625,-504.9130859375}}},
681 {{{-708.0077926931004,-154.61669472244046},
682 {-708.4111557216864,-154.5366642875255},
683 {-32.39227294921875,-906.3277587890625}}},
684 }, {
685 {{{-609.0230951752058,-267.5435593490574},
686 {-594.1120809906336,-136.08492475411555},
687 {505.58447265625,-504.9130859375}}},
688 {{{-609.0230951752058,-267.5435593490574},
689 {-693.7467719138988,-341.3259237831895},
690 {-32.39227294921875,-906.3277587890625}}}
691 }, {
692 {{{-708.0077926931004,-154.61669472244046},
693 {-707.9994640658723,-154.58588461064852},
694 {505.58447265625,-504.9130859375}}},
695 {{{-708.0077926931004,-154.61669472244046},
696 {-708.0239418990758,-154.6403553507124},
697 {-32.39227294921875,-906.3277587890625}}}
698 }, {
699 {{{-708.0077926931004,-154.61669472244046},
700 {-707.9993222215099,-154.55999389855003},
701 {68.88981098017803,296.9273945411635}}},
702 {{{-708.0077926931004,-154.61669472244046},
703 {-708.0509091919608,-154.64675214697067},
704 {-777.4871194247767,-995.1470120113145}}}
705 }, {
706 {{{-708.0077926931004,-154.61669472244046},
707 {-708.0060491116379,-154.60889321524968},
708 {229.97088707895057,-430.0569357467175}}},
709 {{{-708.0077926931004,-154.61669472244046},
710 {-708.013911296257,-154.6219143988058},
711 {138.13162892614037,-573.3689311737394}}}
712 }, {
713 {{{-543.2570545751013,-237.29243831075053},
714 {-452.4119186056987,-143.47223056267802},
715 {229.97088707895057,-430.0569357467175}}},
716 {{{-543.2570545751013,-237.29243831075053},
717 {-660.5330371214436,-362.0016148388},
718 {138.13162892614037,-573.3689311737394}}},
719 },
720};
721
722static double endCtrlRatio(const SkDQuad quad) {
723 SkDVector longEdge = quad[2] - quad[0];
724 double longLen = longEdge.length();
725 SkDVector shortEdge = quad[1] - quad[0];
726 double shortLen = shortEdge.length();
727 return longLen / shortLen;
728}
729
730static void computeMV(const SkDQuad& quad, const SkDVector& v, double m, SkDVector mV[2]) {
731 SkDPoint mPta = {quad[1].fX - m * v.fY, quad[1].fY + m * v.fX};
732 SkDPoint mPtb = {quad[1].fX + m * v.fY, quad[1].fY - m * v.fX};
733 mV[0] = mPta - quad[0];
734 mV[1] = mPtb - quad[0];
735}
736
737static double mDistance(skiatest::Reporter* reporter, bool agrees, const SkDQuad& q1,
738 const SkDQuad& q2) {
739 if (1 && agrees) {
740 return SK_ScalarMax;
741 }
742 // how close is the angle from inflecting in the opposite direction?
743 SkDVector v1 = q1[1] - q1[0];
744 SkDVector v2 = q2[1] - q2[0];
745 double dir = v1.crossCheck(v2);
746 REPORTER_ASSERT(reporter, dir != 0);
747 // solve for opposite direction displacement scale factor == m
748 // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x
749 // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1]
750 // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x)
751 // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x)
752 // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x
753 // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y)
754 // m = v1.cross(v2) / v1.dot(v2)
755 double div = v1.dot(v2);
756 REPORTER_ASSERT(reporter, div != 0);
757 double m = dir / div;
758 SkDVector mV1[2], mV2[2];
759 computeMV(q1, v1, m, mV1);
760 computeMV(q2, v2, m, mV2);
761 double dist1 = v1.length() * m;
762 double dist2 = v2.length() * m;
763 if (gPathOpsAngleIdeasVerbose) {
764 SkDebugf("%c r1=%1.9g r2=%1.9g m=%1.9g dist1=%1.9g dist2=%1.9g "
765 " dir%c 1a=%1.9g 1b=%1.9g 2a=%1.9g 2b=%1.9g\n", agrees ? 'T' : 'F',
766 endCtrlRatio(q1), endCtrlRatio(q2), m, dist1, dist2, dir > 0 ? '+' : '-',
767 mV1[0].crossCheck(v2), mV1[1].crossCheck(v2),
768 mV2[0].crossCheck(v1), mV2[1].crossCheck(v1));
769 }
770 if (1) {
771 bool use1 = fabs(dist1) < fabs(dist2);
772 if (gPathOpsAngleIdeasVerbose) {
773 SkDebugf("%c dist=%1.9g r=%1.9g\n", agrees ? 'T' : 'F', use1 ? dist1 : dist2,
774 use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
775 }
776 return fabs(use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
777 }
778 return SK_ScalarMax;
779}
780
781static void midPointAgrees(skiatest::Reporter* reporter, const SkDQuad& q1, const SkDQuad& q2,
782 bool ccw) {
783 SkDPoint mid1 = q1.ptAtT(0.5);
784 SkDVector m1 = mid1 - q1[0];
785 SkDPoint mid2 = q2.ptAtT(0.5);
786 SkDVector m2 = mid2 - q2[0];
787 REPORTER_ASSERT(reporter, ccw ? m1.crossCheck(m2) < 0 : m1.crossCheck(m2) > 0);
788}
789
790DEF_TEST(PathOpsAngleExtreme, reporter) {
791 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
792 return;
793 }
794 double maxR = SK_ScalarMax;
795 for (int index = 0; index < (int) SK_ARRAY_COUNT(extremeTests); ++index) {
796 const SkDQuad& quad1 = extremeTests[index][0];
797 const SkDQuad& quad2 = extremeTests[index][1];
798 if (gPathOpsAngleIdeasVerbose) {
799 SkDebugf("%s %d\n", __FUNCTION__, index);
800 }
801 REPORTER_ASSERT(reporter, quad1[0] == quad2[0]);
802 SkIntersections i;
803 i.intersect(quad1, quad2);
804 REPORTER_ASSERT(reporter, i.used() == 1);
805 REPORTER_ASSERT(reporter, i.pt(0) == quad1[0]);
806 int overlap = quadHullsOverlap(reporter, quad1, quad2);
807 REPORTER_ASSERT(reporter, overlap >= 0);
808 SkDVector sweep[2], tweep[2];
809 setQuadHullSweep(quad1, sweep);
810 setQuadHullSweep(quad2, tweep);
811 double s0xt0 = sweep[0].crossCheck(tweep[0]);
812 REPORTER_ASSERT(reporter, s0xt0 != 0);
813 bool ccw = s0xt0 < 0;
814 bool agrees = bruteForceCheck(reporter, quad1, quad2, ccw);
815 maxR = SkTMin(maxR, mDistance(reporter, agrees, quad1, quad2));
816 if (agrees) {
817 continue;
818 }
819 midPointAgrees(reporter, quad1, quad2, !ccw);
820 SkDQuad q1 = quad1;
821 SkDQuad q2 = quad2;
822 double loFail = 1;
823 double hiPass = 2;
824 // double vectors until t passes
825 do {
826 q1[1].fX = quad1[0].fX * (1 - hiPass) + quad1[1].fX * hiPass;
827 q1[1].fY = quad1[0].fY * (1 - hiPass) + quad1[1].fY * hiPass;
828 q2[1].fX = quad2[0].fX * (1 - hiPass) + quad2[1].fX * hiPass;
829 q2[1].fY = quad2[0].fY * (1 - hiPass) + quad2[1].fY * hiPass;
830 agrees = bruteForceCheck(reporter, q1, q2, ccw);
831 maxR = SkTMin(maxR, mDistance(reporter, agrees, q1, q2));
832 if (agrees) {
833 break;
834 }
835 midPointAgrees(reporter, quad1, quad2, !ccw);
836 loFail = hiPass;
837 hiPass *= 2;
838 } while (true);
839 // binary search to find minimum pass
840 double midTest = (loFail + hiPass) / 2;
841 double step = (hiPass - loFail) / 4;
842 while (step > FLT_EPSILON) {
843 q1[1].fX = quad1[0].fX * (1 - midTest) + quad1[1].fX * midTest;
844 q1[1].fY = quad1[0].fY * (1 - midTest) + quad1[1].fY * midTest;
845 q2[1].fX = quad2[0].fX * (1 - midTest) + quad2[1].fX * midTest;
846 q2[1].fY = quad2[0].fY * (1 - midTest) + quad2[1].fY * midTest;
847 agrees = bruteForceCheck(reporter, q1, q2, ccw);
848 maxR = SkTMin(maxR, mDistance(reporter, agrees, q1, q2));
849 if (!agrees) {
850 midPointAgrees(reporter, quad1, quad2, !ccw);
851 }
852 midTest += agrees ? -step : step;
853 step /= 2;
854 }
855#ifdef SK_DEBUG
856// DumpQ(q1, q2, 999);
857#endif
858 }
859 if (gPathOpsAngleIdeasVerbose) {
860 SkDebugf("maxR=%1.9g\n", maxR);
861 }
862}