1 /*
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 "include/private/SkTArray.h"
8 #include "include/utils/SkRandom.h"
9 #include "src/core/SkTSort.h"
10 #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"
15
16 static bool gPathOpsAngleIdeasVerbose = false;
17 static bool gPathOpsAngleIdeasEnableBruteCheck = false;
18
19 class PathOpsAngleTester {
20 public:
ConvexHullOverlaps(SkOpAngle & lh,SkOpAngle & rh)21 static int ConvexHullOverlaps(SkOpAngle& lh, SkOpAngle& rh) {
22 return lh.convexHullOverlaps(&rh);
23 }
24
EndsIntersect(SkOpAngle & lh,SkOpAngle & rh)25 static int EndsIntersect(SkOpAngle& lh, SkOpAngle& rh) {
26 return lh.endsIntersect(&rh);
27 }
28 };
29
30 struct 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
testArc(skiatest::Reporter * reporter,const SkDQuad & quad,const SkDQuad & arcRef,int octant)41 static 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
orderQuads(skiatest::Reporter * reporter,const SkDQuad & quad,double radius,SkTArray<double,false> * tArray)74 static 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 QuadPts 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 SkDQuad cQuad;
90 cQuad.debugSet(circle[octant].fPts);
91 double t = testArc(reporter, quad, cQuad, octant);
92 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);
102 next: ;
103 }
104 }
105
quadAngle(skiatest::Reporter * reporter,const SkDQuad & quad,double t)106 static 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
angleDirection(double a1,double a2)113 static bool angleDirection(double a1, double a2) {
114 double delta = a1 - a2;
115 return (delta < 4 && delta > 0) || delta < -4;
116 }
117
setQuadHullSweep(const SkDQuad & quad,SkDVector sweep[2])118 static void setQuadHullSweep(const SkDQuad& quad, SkDVector sweep[2]) {
119 sweep[0] = quad[1] - quad[0];
120 sweep[1] = quad[2] - quad[0];
121 }
122
distEndRatio(double dist,const SkDQuad & quad)123 static double distEndRatio(double dist, const SkDQuad& quad) {
124 SkDVector v[] = {quad[2] - quad[0], quad[1] - quad[0], quad[2] - quad[1]};
125 double longest = std::max(v[0].length(), std::max(v[1].length(), v[2].length()));
126 return longest / dist;
127 }
128
checkParallel(skiatest::Reporter * reporter,const SkDQuad & quad1,const SkDQuad & quad2)129 static 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 */
quadHullsOverlap(skiatest::Reporter * reporter,const SkDQuad & quad1,const SkDQuad & quad2)163 static 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
radianSweep(double start,double end)210 static 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
radianBetween(double start,double test,double end)220 static 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
orderTRange(skiatest::Reporter * reporter,const SkDQuad & quad1,const SkDQuad & quad2,double r,TRange * result)228 static 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);
232 orderQuads(reporter,quad2, r, &t2Array);
233 if (!t1Array.count() || !t2Array.count()) {
234 return false;
235 }
236 SkTQSort<double>(t1Array.begin(), t1Array.end());
237 SkTQSort<double>(t2Array.begin(), t2Array.end());
238 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;
248 result->tMin = std::min(t1, t2);
249 result->a1 = a1;
250 result->a2 = a2;
251 result->ccw = refCCW;
252 return true;
253 }
254
equalPoints(const SkDPoint & pt1,const SkDPoint & pt2,double max)255 static 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
maxDist(const SkDQuad & quad)260 static 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) {
271 max = std::max(max, corner[index].length());
272 }
273 return max;
274 }
275
maxQuad(const SkDQuad & quad)276 static double maxQuad(const SkDQuad& quad) {
277 double max = 0;
278 for (int index = 0; index < 2; ++index) {
279 max = std::max(max, fabs(quad[index].fX));
280 max = std::max(max, fabs(quad[index].fY));
281 }
282 return max;
283 }
284
bruteMinT(skiatest::Reporter * reporter,const SkDQuad & quad1,const SkDQuad & quad2,TRange * lowerRange,TRange * upperRange)285 static bool bruteMinT(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
286 TRange* lowerRange, TRange* upperRange) {
287 double maxRadius = std::min(maxDist(quad1), maxDist(quad2));
288 double maxQuads = std::max(maxQuad(quad1), maxQuad(quad2));
289 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 if (bestR >= maxRadius) {
338 SkDebugf("");
339 }
340 REPORTER_ASSERT(reporter, bestR < maxRadius);
341 return false;
342 }
343 double lastHighR = bestR;
344 r = bestR / 2;
345 rStep = r / 2;
346 do { // find lower bounds of single result
347 TRange tRange;
348 bool success = orderTRange(reporter, quad1, quad2, r, &tRange);
349 if (success) {
350 if (gPathOpsAngleIdeasVerbose) {
351 SkDebugf("l bestCCW=%d ccw=%d bestMin=%1.9g:%1.9g r=%1.9g tMin=%1.9g\n",
352 bestCCW, tRange.ccw, lowerRange->tMin, upperRange->tMin, r,
353 tRange.tMin);
354 }
355 if (bestCCW != (int) tRange.ccw || upperRange->tMin < tRange.tMin) {
356 bestCCW = tRange.ccw;
357 *upperRange = tRange;
358 bestR = lastHighR;
359 break; // need to establish a new upper bounds
360 }
361 SkDPoint pt1 = quad1.ptAtT(tRange.t1);
362 SkDPoint pt2 = quad2.ptAtT(tRange.t2);
363 if (equalPoints(pt1, best1, maxQuads)) {
364 goto breakOut;
365 }
366 best1 = pt1;
367 if (equalPoints(pt2, best2, maxQuads)) {
368 goto breakOut;
369 }
370 best2 = pt2;
371 if (equalPoints(pt1, pt2, maxQuads)) {
372 success = false;
373 } else {
374 if (upperRange->tMin < tRange.tMin) {
375 *upperRange = tRange;
376 }
377 if (lowerRange->tMin > tRange.tMin) {
378 *lowerRange = tRange;
379 }
380 }
381 lastHighR = std::min(r, lastHighR);
382 }
383 r += success ? -rStep : rStep;
384 rStep /= 2;
385 } while (rStep > FLT_EPSILON);
386 } while (rStep > FLT_EPSILON);
387 breakOut:
388 if (gPathOpsAngleIdeasVerbose) {
389 SkDebugf("l a2-a1==%1.9g\n", lowerRange->a2 - lowerRange->a1);
390 }
391 return true;
392 }
393
bruteForce(skiatest::Reporter * reporter,const SkDQuad & quad1,const SkDQuad & quad2,bool ccw)394 static void bruteForce(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
395 bool ccw) {
396 if (!gPathOpsAngleIdeasEnableBruteCheck) {
397 return;
398 }
399 TRange lowerRange, upperRange;
400 bool result = bruteMinT(reporter, quad1, quad2, &lowerRange, &upperRange);
401 REPORTER_ASSERT(reporter, result);
402 double angle = fabs(lowerRange.a2 - lowerRange.a1);
403 REPORTER_ASSERT(reporter, angle > 3.998 || ccw == upperRange.ccw);
404 }
405
bruteForceCheck(skiatest::Reporter * reporter,const SkDQuad & quad1,const SkDQuad & quad2,bool ccw)406 static bool bruteForceCheck(skiatest::Reporter* reporter, const SkDQuad& quad1,
407 const SkDQuad& quad2, bool ccw) {
408 TRange lowerRange, upperRange;
409 bool result = bruteMinT(reporter, quad1, quad2, &lowerRange, &upperRange);
410 REPORTER_ASSERT(reporter, result);
411 return ccw == upperRange.ccw;
412 }
413
makeSegment(SkOpContour * contour,const SkDQuad & quad,SkPoint shortQuad[3])414 static void makeSegment(SkOpContour* contour, const SkDQuad& quad, SkPoint shortQuad[3]) {
415 shortQuad[0] = quad[0].asSkPoint();
416 shortQuad[1] = quad[1].asSkPoint();
417 shortQuad[2] = quad[2].asSkPoint();
418 contour->addQuad(shortQuad);
419 }
420
testQuadAngles(skiatest::Reporter * reporter,const SkDQuad & quad1,const SkDQuad & quad2,int testNo,SkArenaAlloc * allocator)421 static void testQuadAngles(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
422 int testNo, SkArenaAlloc* allocator) {
423 SkPoint shortQuads[2][3];
424
425 SkOpContourHead contour;
426 SkOpGlobalState state(&contour, allocator SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr));
427 contour.init(&state, false, false);
428 makeSegment(&contour, quad1, shortQuads[0]);
429 makeSegment(&contour, quad1, shortQuads[1]);
430 SkOpSegment* seg1 = contour.first();
431 seg1->debugAddAngle(0, 1);
432 SkOpSegment* seg2 = seg1->next();
433 seg2->debugAddAngle(0, 1);
434 int realOverlap = PathOpsAngleTester::ConvexHullOverlaps(*seg1->debugLastAngle(),
435 *seg2->debugLastAngle());
436 const SkDPoint& origin = quad1[0];
437 REPORTER_ASSERT(reporter, origin == quad2[0]);
438 double a1s = atan2(origin.fY - quad1[1].fY, quad1[1].fX - origin.fX);
439 double a1e = atan2(origin.fY - quad1[2].fY, quad1[2].fX - origin.fX);
440 double a2s = atan2(origin.fY - quad2[1].fY, quad2[1].fX - origin.fX);
441 double a2e = atan2(origin.fY - quad2[2].fY, quad2[2].fX - origin.fX);
442 bool oldSchoolOverlap = radianBetween(a1s, a2s, a1e)
443 || radianBetween(a1s, a2e, a1e) || radianBetween(a2s, a1s, a2e)
444 || radianBetween(a2s, a1e, a2e);
445 int overlap = quadHullsOverlap(reporter, quad1, quad2);
446 bool realMatchesOverlap = realOverlap == overlap || SK_ScalarPI - fabs(a2s - a1s) < 0.002;
447 if (realOverlap != overlap) {
448 SkDebugf("\nSK_ScalarPI - fabs(a2s - a1s) = %1.9g\n", SK_ScalarPI - fabs(a2s - a1s));
449 }
450 if (!realMatchesOverlap) {
451 DumpQ(quad1, quad2, testNo);
452 }
453 REPORTER_ASSERT(reporter, realMatchesOverlap);
454 if (oldSchoolOverlap != (overlap < 0)) {
455 overlap = quadHullsOverlap(reporter, quad1, quad2); // set a breakpoint and debug if assert fires
456 REPORTER_ASSERT(reporter, oldSchoolOverlap == (overlap < 0));
457 }
458 SkDVector v1s = quad1[1] - quad1[0];
459 SkDVector v1e = quad1[2] - quad1[0];
460 SkDVector v2s = quad2[1] - quad2[0];
461 SkDVector v2e = quad2[2] - quad2[0];
462 double vDir[2] = { v1s.cross(v1e), v2s.cross(v2e) };
463 bool ray1In2 = v1s.cross(v2s) * vDir[1] <= 0 && v1s.cross(v2e) * vDir[1] >= 0;
464 bool ray2In1 = v2s.cross(v1s) * vDir[0] <= 0 && v2s.cross(v1e) * vDir[0] >= 0;
465 if (overlap >= 0) {
466 // verify that hulls really don't overlap
467 REPORTER_ASSERT(reporter, !ray1In2);
468 REPORTER_ASSERT(reporter, !ray2In1);
469 bool ctrl1In2 = v1e.cross(v2s) * vDir[1] <= 0 && v1e.cross(v2e) * vDir[1] >= 0;
470 REPORTER_ASSERT(reporter, !ctrl1In2);
471 bool ctrl2In1 = v2e.cross(v1s) * vDir[0] <= 0 && v2e.cross(v1e) * vDir[0] >= 0;
472 REPORTER_ASSERT(reporter, !ctrl2In1);
473 // check answer against reference
474 bruteForce(reporter, quad1, quad2, overlap > 0);
475 }
476 // continue end point rays and see if they intersect the opposite curve
477 SkDLine rays[] = {{{origin, quad2[2]}}, {{origin, quad1[2]}}};
478 const SkDQuad* quads[] = {&quad1, &quad2};
479 SkDVector midSpokes[2];
480 SkIntersections intersect[2];
481 double minX, minY, maxX, maxY;
482 minX = minY = SK_ScalarInfinity;
483 maxX = maxY = -SK_ScalarInfinity;
484 double maxWidth = 0;
485 bool useIntersect = false;
486 double smallestTs[] = {1, 1};
487 for (unsigned index = 0; index < SK_ARRAY_COUNT(quads); ++index) {
488 const SkDQuad& q = *quads[index];
489 midSpokes[index] = q.ptAtT(0.5) - origin;
490 minX = std::min(std::min(std::min(minX, origin.fX), q[1].fX), q[2].fX);
491 minY = std::min(std::min(std::min(minY, origin.fY), q[1].fY), q[2].fY);
492 maxX = std::max(std::max(std::max(maxX, origin.fX), q[1].fX), q[2].fX);
493 maxY = std::max(std::max(std::max(maxY, origin.fY), q[1].fY), q[2].fY);
494 maxWidth = std::max(maxWidth, std::max(maxX - minX, maxY - minY));
495 intersect[index].intersectRay(q, rays[index]);
496 const SkIntersections& i = intersect[index];
497 REPORTER_ASSERT(reporter, i.used() >= 1);
498 bool foundZero = false;
499 double smallT = 1;
500 for (int idx2 = 0; idx2 < i.used(); ++idx2) {
501 double t = i[0][idx2];
502 if (t == 0) {
503 foundZero = true;
504 continue;
505 }
506 if (smallT > t) {
507 smallT = t;
508 }
509 }
510 REPORTER_ASSERT(reporter, foundZero == true);
511 if (smallT == 1) {
512 continue;
513 }
514 SkDVector ray = q.ptAtT(smallT) - origin;
515 SkDVector end = rays[index][1] - origin;
516 if (ray.fX * end.fX < 0 || ray.fY * end.fY < 0) {
517 continue;
518 }
519 double rayDist = ray.length();
520 double endDist = end.length();
521 double delta = fabs(rayDist - endDist) / maxWidth;
522 if (delta > 1e-4) {
523 useIntersect ^= true;
524 }
525 smallestTs[index] = smallT;
526 }
527 bool firstInside;
528 if (useIntersect) {
529 int sIndex = (int) (smallestTs[1] < 1);
530 REPORTER_ASSERT(reporter, smallestTs[sIndex ^ 1] == 1);
531 double t = smallestTs[sIndex];
532 const SkDQuad& q = *quads[sIndex];
533 SkDVector ray = q.ptAtT(t) - origin;
534 SkDVector end = rays[sIndex][1] - origin;
535 double rayDist = ray.length();
536 double endDist = end.length();
537 SkDVector mid = q.ptAtT(t / 2) - origin;
538 double midXray = mid.crossCheck(ray);
539 if (gPathOpsAngleIdeasVerbose) {
540 SkDebugf("rayDist>endDist:%d sIndex==0:%d vDir[sIndex]<0:%d midXray<0:%d\n",
541 rayDist > endDist, sIndex == 0, vDir[sIndex] < 0, midXray < 0);
542 }
543 SkASSERT(SkScalarSignAsInt(SkDoubleToScalar(midXray))
544 == SkScalarSignAsInt(SkDoubleToScalar(vDir[sIndex])));
545 firstInside = (rayDist > endDist) ^ (sIndex == 0) ^ (vDir[sIndex] < 0);
546 } else if (overlap >= 0) {
547 return; // answer has already been determined
548 } else {
549 firstInside = checkParallel(reporter, quad1, quad2);
550 }
551 if (overlap < 0) {
552 SkDEBUGCODE(int realEnds =)
553 PathOpsAngleTester::EndsIntersect(*seg1->debugLastAngle(),
554 *seg2->debugLastAngle());
555 SkASSERT(realEnds == (firstInside ? 1 : 0));
556 }
557 bruteForce(reporter, quad1, quad2, firstInside);
558 }
559
DEF_TEST(PathOpsAngleOverlapHullsOne,reporter)560 DEF_TEST(PathOpsAngleOverlapHullsOne, reporter) {
561 SkSTArenaAlloc<4096> allocator;
562 // gPathOpsAngleIdeasVerbose = true;
563 const QuadPts quads[] = {
564 {{{939.4808349609375, 914.355224609375}, {-357.7921142578125, 590.842529296875}, {736.8936767578125, -350.717529296875}}},
565 {{{939.4808349609375, 914.355224609375}, {-182.85418701171875, 634.4552001953125}, {-509.62615966796875, 576.1182861328125}}}
566 };
567 for (int index = 0; index < (int) SK_ARRAY_COUNT(quads); index += 2) {
568 SkDQuad quad0, quad1;
569 quad0.debugSet(quads[index].fPts);
570 quad1.debugSet(quads[index + 1].fPts);
571 testQuadAngles(reporter, quad0, quad1, 0, &allocator);
572 }
573 }
574
DEF_TEST(PathOpsAngleOverlapHulls,reporter)575 DEF_TEST(PathOpsAngleOverlapHulls, reporter) {
576 SkSTArenaAlloc<4096> allocator;
577 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
578 return;
579 }
580 SkRandom ran;
581 for (int index = 0; index < 100000; ++index) {
582 if (index % 1000 == 999) SkDebugf(".");
583 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
584 QuadPts quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
585 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
586 if (quad1.fPts[0] == quad1.fPts[2]) {
587 continue;
588 }
589 QuadPts quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
590 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
591 if (quad2.fPts[0] == quad2.fPts[2]) {
592 continue;
593 }
594 SkIntersections i;
595 SkDQuad q1, q2;
596 q1.debugSet(quad1.fPts);
597 q2.debugSet(quad2.fPts);
598 i.intersect(q1, q2);
599 REPORTER_ASSERT(reporter, i.used() >= 1);
600 if (i.used() > 1) {
601 continue;
602 }
603 testQuadAngles(reporter, q1, q2, index, &allocator);
604 }
605 }
606
DEF_TEST(PathOpsAngleBruteT,reporter)607 DEF_TEST(PathOpsAngleBruteT, reporter) {
608 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
609 return;
610 }
611 SkRandom ran;
612 double smaller = SK_Scalar1;
613 SkDQuad small[2];
614 SkDEBUGCODE(int smallIndex = 0);
615 for (int index = 0; index < 100000; ++index) {
616 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
617 QuadPts quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
618 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
619 if (quad1.fPts[0] == quad1.fPts[2]) {
620 continue;
621 }
622 QuadPts quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
623 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
624 if (quad2.fPts[0] == quad2.fPts[2]) {
625 continue;
626 }
627 SkDQuad q1, q2;
628 q1.debugSet(quad1.fPts);
629 q2.debugSet(quad2.fPts);
630 SkIntersections i;
631 i.intersect(q1, q2);
632 REPORTER_ASSERT(reporter, i.used() >= 1);
633 if (i.used() > 1) {
634 continue;
635 }
636 TRange lowerRange, upperRange;
637 bool result = bruteMinT(reporter, q1, q2, &lowerRange, &upperRange);
638 REPORTER_ASSERT(reporter, result);
639 double min = std::min(upperRange.t1, upperRange.t2);
640 if (smaller > min) {
641 small[0] = q1;
642 small[1] = q2;
643 SkDEBUGCODE(smallIndex = index);
644 smaller = min;
645 }
646 }
647 #ifdef SK_DEBUG
648 DumpQ(small[0], small[1], smallIndex);
649 #endif
650 }
651
DEF_TEST(PathOpsAngleBruteTOne,reporter)652 DEF_TEST(PathOpsAngleBruteTOne, reporter) {
653 // gPathOpsAngleIdeasVerbose = true;
654 const QuadPts qPts[] = {
655 {{{-770.8492431640625, 948.2369384765625}, {-853.37066650390625, 972.0301513671875}, {-200.62042236328125, -26.7174072265625}}},
656 {{{-770.8492431640625, 948.2369384765625}, {513.602783203125, 578.8681640625}, {960.641357421875, -813.69757080078125}}},
657 {{{563.8267822265625, -107.4566650390625}, {-44.67724609375, -136.57452392578125}, {492.3856201171875, -268.79644775390625}}},
658 {{{563.8267822265625, -107.4566650390625}, {708.049072265625, -100.77789306640625}, {-48.88226318359375, 967.9022216796875}}},
659 {{{598.857421875, 846.345458984375}, {-644.095703125, -316.12921142578125}, {-97.64599609375, 20.6158447265625}}},
660 {{{598.857421875, 846.345458984375}, {715.7142333984375, 955.3599853515625}, {-919.9478759765625, 691.611328125}}},
661 };
662 TRange lowerRange, upperRange;
663 SkDQuad quads[SK_ARRAY_COUNT(qPts)];
664 for (int index = 0; index < (int) SK_ARRAY_COUNT(qPts); ++index) {
665 quads[index].debugSet(qPts[index].fPts);
666 }
667 bruteMinT(reporter, quads[0], quads[1], &lowerRange, &upperRange);
668 bruteMinT(reporter, quads[2], quads[3], &lowerRange, &upperRange);
669 bruteMinT(reporter, quads[4], quads[5], &lowerRange, &upperRange);
670 }
671
672 /*
673 The sorting problem happens when the inital tangents are not a true indicator of the curve direction
674 Nearly always, the initial tangents do give the right answer,
675 so the trick is to figure out when the initial tangent cannot be trusted.
676 If the convex hulls of both curves are in the same half plane, and not overlapping, sorting the
677 hulls is enough.
678 If the hulls overlap, and have the same general direction, then intersect the shorter end point ray
679 with the opposing curve, and see on which side of the shorter curve the opposing intersection lies.
680 Otherwise, if the control vector is extremely short, likely the point on curve must be computed
681 If moving the control point slightly can change the sign of the cross product, either answer could
682 be "right".
683 We need to determine how short is extremely short. Move the control point a set percentage of
684 the largest length to determine how stable the curve is vis-a-vis the initial tangent.
685 */
686
687 static const QuadPts extremeTests[][2] = {
688 {
689 {{{-708.0077926931004,-154.61669472244046},
690 {-707.9234268635319,-154.30459999551294},
691 {505.58447265625,-504.9130859375}}},
692 {{{-708.0077926931004,-154.61669472244046},
693 {-711.127526325141,-163.9446090624656},
694 {-32.39227294921875,-906.3277587890625}}},
695 }, {
696 {{{-708.0077926931004,-154.61669472244046},
697 {-708.2875337527566,-154.36676458635623},
698 {505.58447265625,-504.9130859375}}},
699 {{{-708.0077926931004,-154.61669472244046},
700 {-708.4111557216864,-154.5366642875255},
701 {-32.39227294921875,-906.3277587890625}}},
702 }, {
703 {{{-609.0230951752058,-267.5435593490574},
704 {-594.1120809906336,-136.08492475411555},
705 {505.58447265625,-504.9130859375}}},
706 {{{-609.0230951752058,-267.5435593490574},
707 {-693.7467719138988,-341.3259237831895},
708 {-32.39227294921875,-906.3277587890625}}}
709 }, {
710 {{{-708.0077926931004,-154.61669472244046},
711 {-707.9994640658723,-154.58588461064852},
712 {505.58447265625,-504.9130859375}}},
713 {{{-708.0077926931004,-154.61669472244046},
714 {-708.0239418990758,-154.6403553507124},
715 {-32.39227294921875,-906.3277587890625}}}
716 }, {
717 {{{-708.0077926931004,-154.61669472244046},
718 {-707.9993222215099,-154.55999389855003},
719 {68.88981098017803,296.9273945411635}}},
720 {{{-708.0077926931004,-154.61669472244046},
721 {-708.0509091919608,-154.64675214697067},
722 {-777.4871194247767,-995.1470120113145}}}
723 }, {
724 {{{-708.0077926931004,-154.61669472244046},
725 {-708.0060491116379,-154.60889321524968},
726 {229.97088707895057,-430.0569357467175}}},
727 {{{-708.0077926931004,-154.61669472244046},
728 {-708.013911296257,-154.6219143988058},
729 {138.13162892614037,-573.3689311737394}}}
730 }, {
731 {{{-543.2570545751013,-237.29243831075053},
732 {-452.4119186056987,-143.47223056267802},
733 {229.97088707895057,-430.0569357467175}}},
734 {{{-543.2570545751013,-237.29243831075053},
735 {-660.5330371214436,-362.0016148388},
736 {138.13162892614037,-573.3689311737394}}},
737 },
738 };
739
endCtrlRatio(const SkDQuad quad)740 static double endCtrlRatio(const SkDQuad quad) {
741 SkDVector longEdge = quad[2] - quad[0];
742 double longLen = longEdge.length();
743 SkDVector shortEdge = quad[1] - quad[0];
744 double shortLen = shortEdge.length();
745 return longLen / shortLen;
746 }
747
computeMV(const SkDQuad & quad,const SkDVector & v,double m,SkDVector mV[2])748 static void computeMV(const SkDQuad& quad, const SkDVector& v, double m, SkDVector mV[2]) {
749 SkDPoint mPta = {quad[1].fX - m * v.fY, quad[1].fY + m * v.fX};
750 SkDPoint mPtb = {quad[1].fX + m * v.fY, quad[1].fY - m * v.fX};
751 mV[0] = mPta - quad[0];
752 mV[1] = mPtb - quad[0];
753 }
754
mDistance(skiatest::Reporter * reporter,bool agrees,const SkDQuad & q1,const SkDQuad & q2)755 static double mDistance(skiatest::Reporter* reporter, bool agrees, const SkDQuad& q1,
756 const SkDQuad& q2) {
757 if (1 && agrees) {
758 return SK_ScalarMax;
759 }
760 // how close is the angle from inflecting in the opposite direction?
761 SkDVector v1 = q1[1] - q1[0];
762 SkDVector v2 = q2[1] - q2[0];
763 double dir = v1.crossCheck(v2);
764 REPORTER_ASSERT(reporter, dir != 0);
765 // solve for opposite direction displacement scale factor == m
766 // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x
767 // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1]
768 // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x)
769 // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x)
770 // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x
771 // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y)
772 // m = v1.cross(v2) / v1.dot(v2)
773 double div = v1.dot(v2);
774 REPORTER_ASSERT(reporter, div != 0);
775 double m = dir / div;
776 SkDVector mV1[2], mV2[2];
777 computeMV(q1, v1, m, mV1);
778 computeMV(q2, v2, m, mV2);
779 double dist1 = v1.length() * m;
780 double dist2 = v2.length() * m;
781 if (gPathOpsAngleIdeasVerbose) {
782 SkDebugf("%c r1=%1.9g r2=%1.9g m=%1.9g dist1=%1.9g dist2=%1.9g "
783 " dir%c 1a=%1.9g 1b=%1.9g 2a=%1.9g 2b=%1.9g\n", agrees ? 'T' : 'F',
784 endCtrlRatio(q1), endCtrlRatio(q2), m, dist1, dist2, dir > 0 ? '+' : '-',
785 mV1[0].crossCheck(v2), mV1[1].crossCheck(v2),
786 mV2[0].crossCheck(v1), mV2[1].crossCheck(v1));
787 }
788 if (1) {
789 bool use1 = fabs(dist1) < fabs(dist2);
790 if (gPathOpsAngleIdeasVerbose) {
791 SkDebugf("%c dist=%1.9g r=%1.9g\n", agrees ? 'T' : 'F', use1 ? dist1 : dist2,
792 use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
793 }
794 return fabs(use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
795 }
796 return SK_ScalarMax;
797 }
798
midPointAgrees(skiatest::Reporter * reporter,const SkDQuad & q1,const SkDQuad & q2,bool ccw)799 static void midPointAgrees(skiatest::Reporter* reporter, const SkDQuad& q1, const SkDQuad& q2,
800 bool ccw) {
801 SkDPoint mid1 = q1.ptAtT(0.5);
802 SkDVector m1 = mid1 - q1[0];
803 SkDPoint mid2 = q2.ptAtT(0.5);
804 SkDVector m2 = mid2 - q2[0];
805 REPORTER_ASSERT(reporter, ccw ? m1.crossCheck(m2) < 0 : m1.crossCheck(m2) > 0);
806 }
807
DEF_TEST(PathOpsAngleExtreme,reporter)808 DEF_TEST(PathOpsAngleExtreme, reporter) {
809 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
810 return;
811 }
812 double maxR = SK_ScalarMax;
813 for (int index = 0; index < (int) SK_ARRAY_COUNT(extremeTests); ++index) {
814 const QuadPts& qu1 = extremeTests[index][0];
815 const QuadPts& qu2 = extremeTests[index][1];
816 SkDQuad quad1, quad2;
817 quad1.debugSet(qu1.fPts);
818 quad2.debugSet(qu2.fPts);
819 if (gPathOpsAngleIdeasVerbose) {
820 SkDebugf("%s %d\n", __FUNCTION__, index);
821 }
822 REPORTER_ASSERT(reporter, quad1[0] == quad2[0]);
823 SkIntersections i;
824 i.intersect(quad1, quad2);
825 REPORTER_ASSERT(reporter, i.used() == 1);
826 REPORTER_ASSERT(reporter, i.pt(0) == quad1[0]);
827 int overlap = quadHullsOverlap(reporter, quad1, quad2);
828 REPORTER_ASSERT(reporter, overlap >= 0);
829 SkDVector sweep[2], tweep[2];
830 setQuadHullSweep(quad1, sweep);
831 setQuadHullSweep(quad2, tweep);
832 double s0xt0 = sweep[0].crossCheck(tweep[0]);
833 REPORTER_ASSERT(reporter, s0xt0 != 0);
834 bool ccw = s0xt0 < 0;
835 bool agrees = bruteForceCheck(reporter, quad1, quad2, ccw);
836 maxR = std::min(maxR, mDistance(reporter, agrees, quad1, quad2));
837 if (agrees) {
838 continue;
839 }
840 midPointAgrees(reporter, quad1, quad2, !ccw);
841 SkDQuad q1 = quad1;
842 SkDQuad q2 = quad2;
843 double loFail = 1;
844 double hiPass = 2;
845 // double vectors until t passes
846 do {
847 q1[1].fX = quad1[0].fX * (1 - hiPass) + quad1[1].fX * hiPass;
848 q1[1].fY = quad1[0].fY * (1 - hiPass) + quad1[1].fY * hiPass;
849 q2[1].fX = quad2[0].fX * (1 - hiPass) + quad2[1].fX * hiPass;
850 q2[1].fY = quad2[0].fY * (1 - hiPass) + quad2[1].fY * hiPass;
851 agrees = bruteForceCheck(reporter, q1, q2, ccw);
852 maxR = std::min(maxR, mDistance(reporter, agrees, q1, q2));
853 if (agrees) {
854 break;
855 }
856 midPointAgrees(reporter, quad1, quad2, !ccw);
857 loFail = hiPass;
858 hiPass *= 2;
859 } while (true);
860 // binary search to find minimum pass
861 double midTest = (loFail + hiPass) / 2;
862 double step = (hiPass - loFail) / 4;
863 while (step > FLT_EPSILON) {
864 q1[1].fX = quad1[0].fX * (1 - midTest) + quad1[1].fX * midTest;
865 q1[1].fY = quad1[0].fY * (1 - midTest) + quad1[1].fY * midTest;
866 q2[1].fX = quad2[0].fX * (1 - midTest) + quad2[1].fX * midTest;
867 q2[1].fY = quad2[0].fY * (1 - midTest) + quad2[1].fY * midTest;
868 agrees = bruteForceCheck(reporter, q1, q2, ccw);
869 maxR = std::min(maxR, mDistance(reporter, agrees, q1, q2));
870 if (!agrees) {
871 midPointAgrees(reporter, quad1, quad2, !ccw);
872 }
873 midTest += agrees ? -step : step;
874 step /= 2;
875 }
876 #ifdef SK_DEBUG
877 // DumpQ(q1, q2, 999);
878 #endif
879 }
880 if (gPathOpsAngleIdeasVerbose) {
881 SkDebugf("maxR=%1.9g\n", maxR);
882 }
883 }
884