blob: 2887b28febbc2b5dda2f1ec7d1df172704e8e96d [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"
9#include "SkOpSegment.h"
10#include "SkPathOpsTriangle.h"
11#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:
21 static int ConvexHullOverlaps(const SkOpAngle& lh, const SkOpAngle& rh) {
22 return lh.convexHullOverlaps(rh);
23 }
24
25 static int EndsIntersect(const SkOpAngle& lh, const SkOpAngle& rh) {
26 return lh.endsIntersect(rh);
27 }
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
409class PathOpsSegmentTester {
410public:
411 static void ConstructQuad(SkOpSegment* segment, SkPoint shortQuad[3]) {
412 segment->debugConstructQuad(shortQuad);
413 }
414};
415
416static void makeSegment(const SkDQuad& quad, SkPoint shortQuad[3], SkOpSegment* result) {
417 shortQuad[0] = quad[0].asSkPoint();
418 shortQuad[1] = quad[1].asSkPoint();
419 shortQuad[2] = quad[2].asSkPoint();
420 PathOpsSegmentTester::ConstructQuad(result, shortQuad);
421}
422
423static void testQuadAngles(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
424 int testNo) {
425 SkPoint shortQuads[2][3];
426 SkOpSegment seg[2];
427 makeSegment(quad1, shortQuads[0], &seg[0]);
428 makeSegment(quad2, shortQuads[1], &seg[1]);
429 int realOverlap = PathOpsAngleTester::ConvexHullOverlaps(seg[0].angle(0), seg[1].angle(0));
430 const SkDPoint& origin = quad1[0];
431 REPORTER_ASSERT(reporter, origin == quad2[0]);
432 double a1s = atan2(origin.fY - quad1[1].fY, quad1[1].fX - origin.fX);
433 double a1e = atan2(origin.fY - quad1[2].fY, quad1[2].fX - origin.fX);
434 double a2s = atan2(origin.fY - quad2[1].fY, quad2[1].fX - origin.fX);
435 double a2e = atan2(origin.fY - quad2[2].fY, quad2[2].fX - origin.fX);
436 bool oldSchoolOverlap = radianBetween(a1s, a2s, a1e)
437 || radianBetween(a1s, a2e, a1e) || radianBetween(a2s, a1s, a2e)
438 || radianBetween(a2s, a1e, a2e);
439 int overlap = quadHullsOverlap(reporter, quad1, quad2);
440 bool realMatchesOverlap = realOverlap == overlap || SK_ScalarPI - fabs(a2s - a1s) < 0.002;
441 if (realOverlap != overlap) {
442 SkDebugf("\nSK_ScalarPI - fabs(a2s - a1s) = %1.9g\n", SK_ScalarPI - fabs(a2s - a1s));
443 }
444 if (!realMatchesOverlap) {
445 DumpQ(quad1, quad2, testNo);
446 }
447 REPORTER_ASSERT(reporter, realMatchesOverlap);
448 if (oldSchoolOverlap != (overlap < 0)) {
449 overlap = quadHullsOverlap(reporter, quad1, quad2); // set a breakpoint and debug if assert fires
450 REPORTER_ASSERT(reporter, oldSchoolOverlap == (overlap < 0));
451 }
452 SkDVector v1s = quad1[1] - quad1[0];
453 SkDVector v1e = quad1[2] - quad1[0];
454 SkDVector v2s = quad2[1] - quad2[0];
455 SkDVector v2e = quad2[2] - quad2[0];
456 double vDir[2] = { v1s.cross(v1e), v2s.cross(v2e) };
457 bool ray1In2 = v1s.cross(v2s) * vDir[1] <= 0 && v1s.cross(v2e) * vDir[1] >= 0;
458 bool ray2In1 = v2s.cross(v1s) * vDir[0] <= 0 && v2s.cross(v1e) * vDir[0] >= 0;
459 if (overlap >= 0) {
460 // verify that hulls really don't overlap
461 REPORTER_ASSERT(reporter, !ray1In2);
462 REPORTER_ASSERT(reporter, !ray2In1);
463 bool ctrl1In2 = v1e.cross(v2s) * vDir[1] <= 0 && v1e.cross(v2e) * vDir[1] >= 0;
464 REPORTER_ASSERT(reporter, !ctrl1In2);
465 bool ctrl2In1 = v2e.cross(v1s) * vDir[0] <= 0 && v2e.cross(v1e) * vDir[0] >= 0;
466 REPORTER_ASSERT(reporter, !ctrl2In1);
467 // check answer against reference
468 bruteForce(reporter, quad1, quad2, overlap > 0);
469 }
470 // continue end point rays and see if they intersect the opposite curve
471 SkDLine rays[] = {{{origin, quad2[2]}}, {{origin, quad1[2]}}};
472 const SkDQuad* quads[] = {&quad1, &quad2};
473 SkDVector midSpokes[2];
474 SkIntersections intersect[2];
475 double minX, minY, maxX, maxY;
476 minX = minY = SK_ScalarInfinity;
477 maxX = maxY = -SK_ScalarInfinity;
478 double maxWidth = 0;
479 bool useIntersect = false;
480 double smallestTs[] = {1, 1};
481 for (unsigned index = 0; index < SK_ARRAY_COUNT(quads); ++index) {
482 const SkDQuad& q = *quads[index];
483 midSpokes[index] = q.ptAtT(0.5) - origin;
484 minX = SkTMin(SkTMin(SkTMin(minX, origin.fX), q[1].fX), q[2].fX);
485 minY = SkTMin(SkTMin(SkTMin(minY, origin.fY), q[1].fY), q[2].fY);
486 maxX = SkTMax(SkTMax(SkTMax(maxX, origin.fX), q[1].fX), q[2].fX);
487 maxY = SkTMax(SkTMax(SkTMax(maxY, origin.fY), q[1].fY), q[2].fY);
488 maxWidth = SkTMax(maxWidth, SkTMax(maxX - minX, maxY - minY));
489 intersect[index].intersectRay(q, rays[index]);
490 const SkIntersections& i = intersect[index];
491 REPORTER_ASSERT(reporter, i.used() >= 1);
492 bool foundZero = false;
493 double smallT = 1;
494 for (int idx2 = 0; idx2 < i.used(); ++idx2) {
495 double t = i[0][idx2];
496 if (t == 0) {
497 foundZero = true;
498 continue;
499 }
500 if (smallT > t) {
501 smallT = t;
502 }
503 }
504 REPORTER_ASSERT(reporter, foundZero == true);
505 if (smallT == 1) {
506 continue;
507 }
508 SkDVector ray = q.ptAtT(smallT) - origin;
509 SkDVector end = rays[index][1] - origin;
510 if (ray.fX * end.fX < 0 || ray.fY * end.fY < 0) {
511 continue;
512 }
513 double rayDist = ray.length();
514 double endDist = end.length();
515 double delta = fabs(rayDist - endDist) / maxWidth;
516 if (delta > 1e-4) {
517 useIntersect ^= true;
518 }
519 smallestTs[index] = smallT;
520 }
521 bool firstInside;
522 if (useIntersect) {
523 int sIndex = (int) (smallestTs[1] < 1);
524 REPORTER_ASSERT(reporter, smallestTs[sIndex ^ 1] == 1);
525 double t = smallestTs[sIndex];
526 const SkDQuad& q = *quads[sIndex];
527 SkDVector ray = q.ptAtT(t) - origin;
528 SkDVector end = rays[sIndex][1] - origin;
529 double rayDist = ray.length();
530 double endDist = end.length();
531 SkDVector mid = q.ptAtT(t / 2) - origin;
532 double midXray = mid.crossCheck(ray);
533 if (gPathOpsAngleIdeasVerbose) {
534 SkDebugf("rayDist>endDist:%d sIndex==0:%d vDir[sIndex]<0:%d midXray<0:%d\n",
535 rayDist > endDist, sIndex == 0, vDir[sIndex] < 0, midXray < 0);
536 }
537 SkASSERT(SkScalarSignAsInt(SkDoubleToScalar(midXray))
538 == SkScalarSignAsInt(SkDoubleToScalar(vDir[sIndex])));
539 firstInside = (rayDist > endDist) ^ (sIndex == 0) ^ (vDir[sIndex] < 0);
540 } else if (overlap >= 0) {
541 return; // answer has already been determined
542 } else {
543 firstInside = checkParallel(reporter, quad1, quad2);
544 }
545 if (overlap < 0) {
546 SkDEBUGCODE(int realEnds =)
547 PathOpsAngleTester::EndsIntersect(seg[0].angle(0), seg[1].angle(0));
548 SkASSERT(realEnds == (firstInside ? 1 : 0));
549 }
550 bruteForce(reporter, quad1, quad2, firstInside);
551}
552
553DEF_TEST(PathOpsAngleOverlapHullsOne, reporter) {
554// gPathOpsAngleIdeasVerbose = true;
555 const SkDQuad quads[] = {
556{{{939.4808349609375, 914.355224609375}, {-357.7921142578125, 590.842529296875}, {736.8936767578125, -350.717529296875}}},
557{{{939.4808349609375, 914.355224609375}, {-182.85418701171875, 634.4552001953125}, {-509.62615966796875, 576.1182861328125}}}
558 };
559 for (int index = 0; index < (int) SK_ARRAY_COUNT(quads); index += 2) {
560 testQuadAngles(reporter, quads[index], quads[index + 1], 0);
561 }
562}
563
564DEF_TEST(PathOpsAngleOverlapHulls, reporter) {
565 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
566 return;
567 }
568 SkRandom ran;
569 for (int index = 0; index < 100000; ++index) {
570 if (index % 1000 == 999) SkDebugf(".");
571 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
572 SkDQuad quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
573 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
574 if (quad1[0] == quad1[2]) {
575 continue;
576 }
577 SkDQuad quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
578 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
579 if (quad2[0] == quad2[2]) {
580 continue;
581 }
582 SkIntersections i;
583 i.intersect(quad1, quad2);
584 REPORTER_ASSERT(reporter, i.used() >= 1);
585 if (i.used() > 1) {
586 continue;
587 }
588 testQuadAngles(reporter, quad1, quad2, index);
589 }
590}
591
592DEF_TEST(PathOpsAngleBruteT, reporter) {
593 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
594 return;
595 }
596 SkRandom ran;
597 double smaller = SK_Scalar1;
598 SkDQuad small[2];
599 SkDEBUGCODE(int smallIndex);
600 for (int index = 0; index < 100000; ++index) {
601 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
602 SkDQuad quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
603 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
604 if (quad1[0] == quad1[2]) {
605 continue;
606 }
607 SkDQuad quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
608 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
609 if (quad2[0] == quad2[2]) {
610 continue;
611 }
612 SkIntersections i;
613 i.intersect(quad1, quad2);
614 REPORTER_ASSERT(reporter, i.used() >= 1);
615 if (i.used() > 1) {
616 continue;
617 }
618 TRange lowerRange, upperRange;
619 bool result = bruteMinT(reporter, quad1, quad2, &lowerRange, &upperRange);
620 REPORTER_ASSERT(reporter, result);
621 double min = SkTMin(upperRange.t1, upperRange.t2);
622 if (smaller > min) {
623 small[0] = quad1;
624 small[1] = quad2;
625 SkDEBUGCODE(smallIndex = index);
626 smaller = min;
627 }
628 }
629#ifdef SK_DEBUG
630 DumpQ(small[0], small[1], smallIndex);
631#endif
632}
633
634DEF_TEST(PathOpsAngleBruteTOne, reporter) {
635// gPathOpsAngleIdeasVerbose = true;
636 const SkDQuad quads[] = {
637{{{-770.8492431640625, 948.2369384765625}, {-853.37066650390625, 972.0301513671875}, {-200.62042236328125, -26.7174072265625}}},
638{{{-770.8492431640625, 948.2369384765625}, {513.602783203125, 578.8681640625}, {960.641357421875, -813.69757080078125}}},
639{{{563.8267822265625, -107.4566650390625}, {-44.67724609375, -136.57452392578125}, {492.3856201171875, -268.79644775390625}}},
640{{{563.8267822265625, -107.4566650390625}, {708.049072265625, -100.77789306640625}, {-48.88226318359375, 967.9022216796875}}},
641{{{598.857421875, 846.345458984375}, {-644.095703125, -316.12921142578125}, {-97.64599609375, 20.6158447265625}}},
642{{{598.857421875, 846.345458984375}, {715.7142333984375, 955.3599853515625}, {-919.9478759765625, 691.611328125}}},
643 };
644 TRange lowerRange, upperRange;
645 bruteMinT(reporter, quads[0], quads[1], &lowerRange, &upperRange);
646 bruteMinT(reporter, quads[2], quads[3], &lowerRange, &upperRange);
647 bruteMinT(reporter, quads[4], quads[5], &lowerRange, &upperRange);
648}
649
650/*
651The sorting problem happens when the inital tangents are not a true indicator of the curve direction
652Nearly always, the initial tangents do give the right answer,
653so the trick is to figure out when the initial tangent cannot be trusted.
654If the convex hulls of both curves are in the same half plane, and not overlapping, sorting the
655hulls is enough.
656If the hulls overlap, and have the same general direction, then intersect the shorter end point ray
657with the opposing curve, and see on which side of the shorter curve the opposing intersection lies.
658Otherwise, if the control vector is extremely short, likely the point on curve must be computed
659If moving the control point slightly can change the sign of the cross product, either answer could
660be "right".
661We need to determine how short is extremely short. Move the control point a set percentage of
662the largest length to determine how stable the curve is vis-a-vis the initial tangent.
663*/
664
665static const SkDQuad extremeTests[][2] = {
666 {
667 {{{-708.0077926931004,-154.61669472244046},
668 {-707.9234268635319,-154.30459999551294},
669 {505.58447265625,-504.9130859375}}},
670 {{{-708.0077926931004,-154.61669472244046},
671 {-711.127526325141,-163.9446090624656},
672 {-32.39227294921875,-906.3277587890625}}},
673 }, {
674 {{{-708.0077926931004,-154.61669472244046},
675 {-708.2875337527566,-154.36676458635623},
676 {505.58447265625,-504.9130859375}}},
677 {{{-708.0077926931004,-154.61669472244046},
678 {-708.4111557216864,-154.5366642875255},
679 {-32.39227294921875,-906.3277587890625}}},
680 }, {
681 {{{-609.0230951752058,-267.5435593490574},
682 {-594.1120809906336,-136.08492475411555},
683 {505.58447265625,-504.9130859375}}},
684 {{{-609.0230951752058,-267.5435593490574},
685 {-693.7467719138988,-341.3259237831895},
686 {-32.39227294921875,-906.3277587890625}}}
687 }, {
688 {{{-708.0077926931004,-154.61669472244046},
689 {-707.9994640658723,-154.58588461064852},
690 {505.58447265625,-504.9130859375}}},
691 {{{-708.0077926931004,-154.61669472244046},
692 {-708.0239418990758,-154.6403553507124},
693 {-32.39227294921875,-906.3277587890625}}}
694 }, {
695 {{{-708.0077926931004,-154.61669472244046},
696 {-707.9993222215099,-154.55999389855003},
697 {68.88981098017803,296.9273945411635}}},
698 {{{-708.0077926931004,-154.61669472244046},
699 {-708.0509091919608,-154.64675214697067},
700 {-777.4871194247767,-995.1470120113145}}}
701 }, {
702 {{{-708.0077926931004,-154.61669472244046},
703 {-708.0060491116379,-154.60889321524968},
704 {229.97088707895057,-430.0569357467175}}},
705 {{{-708.0077926931004,-154.61669472244046},
706 {-708.013911296257,-154.6219143988058},
707 {138.13162892614037,-573.3689311737394}}}
708 }, {
709 {{{-543.2570545751013,-237.29243831075053},
710 {-452.4119186056987,-143.47223056267802},
711 {229.97088707895057,-430.0569357467175}}},
712 {{{-543.2570545751013,-237.29243831075053},
713 {-660.5330371214436,-362.0016148388},
714 {138.13162892614037,-573.3689311737394}}},
715 },
716};
717
718static double endCtrlRatio(const SkDQuad quad) {
719 SkDVector longEdge = quad[2] - quad[0];
720 double longLen = longEdge.length();
721 SkDVector shortEdge = quad[1] - quad[0];
722 double shortLen = shortEdge.length();
723 return longLen / shortLen;
724}
725
726static void computeMV(const SkDQuad& quad, const SkDVector& v, double m, SkDVector mV[2]) {
727 SkDPoint mPta = {quad[1].fX - m * v.fY, quad[1].fY + m * v.fX};
728 SkDPoint mPtb = {quad[1].fX + m * v.fY, quad[1].fY - m * v.fX};
729 mV[0] = mPta - quad[0];
730 mV[1] = mPtb - quad[0];
731}
732
733static double mDistance(skiatest::Reporter* reporter, bool agrees, const SkDQuad& q1,
734 const SkDQuad& q2) {
735 if (1 && agrees) {
736 return SK_ScalarMax;
737 }
738 // how close is the angle from inflecting in the opposite direction?
739 SkDVector v1 = q1[1] - q1[0];
740 SkDVector v2 = q2[1] - q2[0];
741 double dir = v1.crossCheck(v2);
742 REPORTER_ASSERT(reporter, dir != 0);
743 // solve for opposite direction displacement scale factor == m
744 // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x
745 // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1]
746 // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x)
747 // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x)
748 // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x
749 // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y)
750 // m = v1.cross(v2) / v1.dot(v2)
751 double div = v1.dot(v2);
752 REPORTER_ASSERT(reporter, div != 0);
753 double m = dir / div;
754 SkDVector mV1[2], mV2[2];
755 computeMV(q1, v1, m, mV1);
756 computeMV(q2, v2, m, mV2);
757 double dist1 = v1.length() * m;
758 double dist2 = v2.length() * m;
759 if (gPathOpsAngleIdeasVerbose) {
760 SkDebugf("%c r1=%1.9g r2=%1.9g m=%1.9g dist1=%1.9g dist2=%1.9g "
761 " dir%c 1a=%1.9g 1b=%1.9g 2a=%1.9g 2b=%1.9g\n", agrees ? 'T' : 'F',
762 endCtrlRatio(q1), endCtrlRatio(q2), m, dist1, dist2, dir > 0 ? '+' : '-',
763 mV1[0].crossCheck(v2), mV1[1].crossCheck(v2),
764 mV2[0].crossCheck(v1), mV2[1].crossCheck(v1));
765 }
766 if (1) {
767 bool use1 = fabs(dist1) < fabs(dist2);
768 if (gPathOpsAngleIdeasVerbose) {
769 SkDebugf("%c dist=%1.9g r=%1.9g\n", agrees ? 'T' : 'F', use1 ? dist1 : dist2,
770 use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
771 }
772 return fabs(use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
773 }
774 return SK_ScalarMax;
775}
776
777static void midPointAgrees(skiatest::Reporter* reporter, const SkDQuad& q1, const SkDQuad& q2,
778 bool ccw) {
779 SkDPoint mid1 = q1.ptAtT(0.5);
780 SkDVector m1 = mid1 - q1[0];
781 SkDPoint mid2 = q2.ptAtT(0.5);
782 SkDVector m2 = mid2 - q2[0];
783 REPORTER_ASSERT(reporter, ccw ? m1.crossCheck(m2) < 0 : m1.crossCheck(m2) > 0);
784}
785
786DEF_TEST(PathOpsAngleExtreme, reporter) {
787 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
788 return;
789 }
790 double maxR = SK_ScalarMax;
791 for (int index = 0; index < (int) SK_ARRAY_COUNT(extremeTests); ++index) {
792 const SkDQuad& quad1 = extremeTests[index][0];
793 const SkDQuad& quad2 = extremeTests[index][1];
794 if (gPathOpsAngleIdeasVerbose) {
795 SkDebugf("%s %d\n", __FUNCTION__, index);
796 }
797 REPORTER_ASSERT(reporter, quad1[0] == quad2[0]);
798 SkIntersections i;
799 i.intersect(quad1, quad2);
800 REPORTER_ASSERT(reporter, i.used() == 1);
801 REPORTER_ASSERT(reporter, i.pt(0) == quad1[0]);
802 int overlap = quadHullsOverlap(reporter, quad1, quad2);
803 REPORTER_ASSERT(reporter, overlap >= 0);
804 SkDVector sweep[2], tweep[2];
805 setQuadHullSweep(quad1, sweep);
806 setQuadHullSweep(quad2, tweep);
807 double s0xt0 = sweep[0].crossCheck(tweep[0]);
808 REPORTER_ASSERT(reporter, s0xt0 != 0);
809 bool ccw = s0xt0 < 0;
810 bool agrees = bruteForceCheck(reporter, quad1, quad2, ccw);
811 maxR = SkTMin(maxR, mDistance(reporter, agrees, quad1, quad2));
812 if (agrees) {
813 continue;
814 }
815 midPointAgrees(reporter, quad1, quad2, !ccw);
816 SkDQuad q1 = quad1;
817 SkDQuad q2 = quad2;
818 double loFail = 1;
819 double hiPass = 2;
820 // double vectors until t passes
821 do {
822 q1[1].fX = quad1[0].fX * (1 - hiPass) + quad1[1].fX * hiPass;
823 q1[1].fY = quad1[0].fY * (1 - hiPass) + quad1[1].fY * hiPass;
824 q2[1].fX = quad2[0].fX * (1 - hiPass) + quad2[1].fX * hiPass;
825 q2[1].fY = quad2[0].fY * (1 - hiPass) + quad2[1].fY * hiPass;
826 agrees = bruteForceCheck(reporter, q1, q2, ccw);
827 maxR = SkTMin(maxR, mDistance(reporter, agrees, q1, q2));
828 if (agrees) {
829 break;
830 }
831 midPointAgrees(reporter, quad1, quad2, !ccw);
832 loFail = hiPass;
833 hiPass *= 2;
834 } while (true);
835 // binary search to find minimum pass
836 double midTest = (loFail + hiPass) / 2;
837 double step = (hiPass - loFail) / 4;
838 while (step > FLT_EPSILON) {
839 q1[1].fX = quad1[0].fX * (1 - midTest) + quad1[1].fX * midTest;
840 q1[1].fY = quad1[0].fY * (1 - midTest) + quad1[1].fY * midTest;
841 q2[1].fX = quad2[0].fX * (1 - midTest) + quad2[1].fX * midTest;
842 q2[1].fY = quad2[0].fY * (1 - midTest) + quad2[1].fY * midTest;
843 agrees = bruteForceCheck(reporter, q1, q2, ccw);
844 maxR = SkTMin(maxR, mDistance(reporter, agrees, q1, q2));
845 if (!agrees) {
846 midPointAgrees(reporter, quad1, quad2, !ccw);
847 }
848 midTest += agrees ? -step : step;
849 step /= 2;
850 }
851#ifdef SK_DEBUG
852// DumpQ(q1, q2, 999);
853#endif
854 }
855 if (gPathOpsAngleIdeasVerbose) {
856 SkDebugf("maxR=%1.9g\n", maxR);
857 }
858}