2 * Copyright 2013 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
7 #include "PathOpsTestCommon.h"
8 #include "SkIntersections.h"
9 #include "SkOpSegment.h"
10 #include "SkPathOpsTriangle.h"
16 static bool gPathOpsAngleIdeasVerbose = false;
17 static bool gPathOpsAngleIdeasEnableBruteCheck = false;
19 class PathOpsAngleTester {
21 static int ConvexHullOverlaps(const SkOpAngle& lh, const SkOpAngle& rh) {
22 return lh.convexHullOverlaps(rh);
25 static int EndsIntersect(const SkOpAngle& lh, const SkOpAngle& rh) {
26 return lh.endsIntersect(rh);
41 static double testArc(skiatest::Reporter* reporter, const SkDQuad& quad, const SkDQuad& arcRef,
44 SkDVector offset = {quad[0].fX, quad[0].fY};
49 i.intersect(arc, quad);
55 for (int idx = 0; idx < i.used(); ++idx) {
56 if (i[0][idx] > 1 || i[0][idx] < 0) {
58 i.intersect(arc, quad);
60 if (i[1][idx] > 1 || i[1][idx] < 0) {
62 i.intersect(arc, quad);
69 REPORTER_ASSERT(reporter, smallest >= 0);
70 REPORTER_ASSERT(reporter, t >= 0 && t <= 1);
71 return i[1][smallest];
74 static void orderQuads(skiatest::Reporter* reporter, const SkDQuad& quad, double radius,
75 SkTArray<double, false>* tArray) {
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);
93 for (int index = 0; index < tArray->count(); ++index) {
94 double matchT = (*tArray)[index];
95 if (approximately_equal(t, matchT)) {
104 static 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);
111 static bool angleDirection(double a1, double a2) {
112 double delta = a1 - a2;
113 return (delta < 4 && delta > 0) || delta < -4;
116 static void setQuadHullSweep(const SkDQuad& quad, SkDVector sweep[2]) {
117 sweep[0] = quad[1] - quad[0];
118 sweep[1] = quad[2] - quad[0];
121 static 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;
127 static 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
151 SkDVector m0 = quad1.ptAtT(0.5) - quad1[0];
152 SkDVector m1 = quad2.ptAtT(0.5) - quad2[0];
153 return m0.crossCheck(m1) < 0;
161 static 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]);
177 if ((s0xt0 == 0 && s1xt1 == 0) || (s1xt0 == 0 && s0xt1 == 0)) { // s0 to s1 equals t0 to t1
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;
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) {
189 if (s0xt0 <= 0 && s0xt1 <= 0 && s1xt0 <= 0 && s1xt1 <= 0) {
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) {
201 if (s0xt0 < 0 && m0xm1 < 0) {
204 REPORTER_ASSERT(reporter, s0xt0 != 0);
205 return checkParallel(reporter, quad1, quad2);
208 static 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;
218 static 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);
226 static 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()) {
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)) {
243 bool refCCW = angleDirection(a1, a2);
246 result->tMin = SkTMin(t1, t2);
249 result->ccw = refCCW;
253 static 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);
258 static double maxDist(const SkDQuad& quad) {
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 }
268 for (unsigned index = 0; index < SK_ARRAY_COUNT(corner); ++index) {
269 max = SkTMax(max, corner[index].length());
274 static double maxQuad(const SkDQuad& quad) {
276 for (int index = 0; index < 2; ++index) {
277 max = SkTMax(max, fabs(quad[index].fX));
278 max = SkTMax(max, fabs(quad[index].fY));
283 static 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};
292 double bestR = maxRadius;
293 upperRange->tMin = 0;
294 lowerRange->tMin = 1;
296 do { // find upper bounds of single result
298 bool stepUp = orderTRange(reporter, quad1, quad2, r, &tRange);
300 SkDPoint pt1 = quad1.ptAtT(tRange.t1);
301 if (equalPoints(pt1, best1, maxQuads)) {
305 SkDPoint pt2 = quad2.ptAtT(tRange.t2);
306 if (equalPoints(pt2, best2, maxQuads)) {
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,
315 if (bestCCW >= 0 && bestCCW != (int) tRange.ccw) {
316 if (tRange.tMin < upperRange->tMin) {
317 upperRange->tMin = 0;
322 if (upperRange->tMin < tRange.tMin) {
323 bestCCW = tRange.ccw;
325 *upperRange = tRange;
327 if (lowerRange->tMin > tRange.tMin) {
328 *lowerRange = tRange;
331 r += stepUp ? rStep : -rStep;
333 } while (rStep > FLT_EPSILON);
335 REPORTER_ASSERT(reporter, bestR < maxRadius);
338 double lastHighR = bestR;
341 do { // find lower bounds of single result
343 bool success = orderTRange(reporter, quad1, quad2, r, &tRange);
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,
350 if (bestCCW != (int) tRange.ccw || upperRange->tMin < tRange.tMin) {
351 bestCCW = tRange.ccw;
352 *upperRange = tRange;
354 break; // need to establish a new upper bounds
356 SkDPoint pt1 = quad1.ptAtT(tRange.t1);
357 SkDPoint pt2 = quad2.ptAtT(tRange.t2);
358 if (equalPoints(pt1, best1, maxQuads)) {
362 if (equalPoints(pt2, best2, maxQuads)) {
366 if (equalPoints(pt1, pt2, maxQuads)) {
369 if (upperRange->tMin < tRange.tMin) {
370 *upperRange = tRange;
372 if (lowerRange->tMin > tRange.tMin) {
373 *lowerRange = tRange;
376 lastHighR = SkTMin(r, lastHighR);
378 r += success ? -rStep : rStep;
380 } while (rStep > FLT_EPSILON);
381 } while (rStep > FLT_EPSILON);
383 if (gPathOpsAngleIdeasVerbose) {
384 SkDebugf("l a2-a1==%1.9g\n", lowerRange->a2 - lowerRange->a1);
389 static void bruteForce(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
391 if (!gPathOpsAngleIdeasEnableBruteCheck) {
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);
401 static 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;
409 class PathOpsSegmentTester {
411 static void ConstructQuad(SkOpSegment* segment, SkPoint shortQuad[3]) {
412 segment->debugConstructQuad(shortQuad);
416 static 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);
423 static void testQuadAngles(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2,
425 SkPoint shortQuads[2][3];
427 makeSegment(quad1, shortQuads[0], &seg[0]);
428 makeSegment(quad2, shortQuads[1], &seg[1]);
429 int realOverlap = PathOpsAngleTester::ConvexHullOverlaps(*seg[0].debugLastAngle(),
430 *seg[1].debugLastAngle());
431 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));
445 if (!realMatchesOverlap) {
446 DumpQ(quad1, quad2, testNo);
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));
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;
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);
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;
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;
495 for (int idx2 = 0; idx2 < i.used(); ++idx2) {
496 double t = i[0][idx2];
505 REPORTER_ASSERT(reporter, foundZero == true);
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) {
514 double rayDist = ray.length();
515 double endDist = end.length();
516 double delta = fabs(rayDist - endDist) / maxWidth;
518 useIntersect ^= true;
520 smallestTs[index] = smallT;
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);
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
544 firstInside = checkParallel(reporter, quad1, quad2);
547 SkDEBUGCODE(int realEnds =)
548 PathOpsAngleTester::EndsIntersect(*seg[0].debugLastAngle(),
549 *seg[1].debugLastAngle());
550 SkASSERT(realEnds == (firstInside ? 1 : 0));
552 bruteForce(reporter, quad1, quad2, firstInside);
555 DEF_TEST(PathOpsAngleOverlapHullsOne, reporter) {
556 // gPathOpsAngleIdeasVerbose = true;
557 const SkDQuad quads[] = {
558 {{{939.4808349609375, 914.355224609375}, {-357.7921142578125, 590.842529296875}, {736.8936767578125, -350.717529296875}}},
559 {{{939.4808349609375, 914.355224609375}, {-182.85418701171875, 634.4552001953125}, {-509.62615966796875, 576.1182861328125}}}
561 for (int index = 0; index < (int) SK_ARRAY_COUNT(quads); index += 2) {
562 testQuadAngles(reporter, quads[index], quads[index + 1], 0);
566 DEF_TEST(PathOpsAngleOverlapHulls, reporter) {
567 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
571 for (int index = 0; index < 100000; ++index) {
572 if (index % 1000 == 999) SkDebugf(".");
573 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
574 SkDQuad quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
575 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
576 if (quad1[0] == quad1[2]) {
579 SkDQuad quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
580 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
581 if (quad2[0] == quad2[2]) {
585 i.intersect(quad1, quad2);
586 REPORTER_ASSERT(reporter, i.used() >= 1);
590 testQuadAngles(reporter, quad1, quad2, index);
594 DEF_TEST(PathOpsAngleBruteT, reporter) {
595 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
599 double smaller = SK_Scalar1;
601 SkDEBUGCODE(int smallIndex);
602 for (int index = 0; index < 100000; ++index) {
603 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
604 SkDQuad quad1 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
605 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
606 if (quad1[0] == quad1[2]) {
609 SkDQuad quad2 = {{origin, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
610 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}}};
611 if (quad2[0] == quad2[2]) {
615 i.intersect(quad1, quad2);
616 REPORTER_ASSERT(reporter, i.used() >= 1);
620 TRange lowerRange, upperRange;
621 bool result = bruteMinT(reporter, quad1, quad2, &lowerRange, &upperRange);
622 REPORTER_ASSERT(reporter, result);
623 double min = SkTMin(upperRange.t1, upperRange.t2);
627 SkDEBUGCODE(smallIndex = index);
632 DumpQ(small[0], small[1], smallIndex);
636 DEF_TEST(PathOpsAngleBruteTOne, reporter) {
637 // gPathOpsAngleIdeasVerbose = true;
638 const SkDQuad quads[] = {
639 {{{-770.8492431640625, 948.2369384765625}, {-853.37066650390625, 972.0301513671875}, {-200.62042236328125, -26.7174072265625}}},
640 {{{-770.8492431640625, 948.2369384765625}, {513.602783203125, 578.8681640625}, {960.641357421875, -813.69757080078125}}},
641 {{{563.8267822265625, -107.4566650390625}, {-44.67724609375, -136.57452392578125}, {492.3856201171875, -268.79644775390625}}},
642 {{{563.8267822265625, -107.4566650390625}, {708.049072265625, -100.77789306640625}, {-48.88226318359375, 967.9022216796875}}},
643 {{{598.857421875, 846.345458984375}, {-644.095703125, -316.12921142578125}, {-97.64599609375, 20.6158447265625}}},
644 {{{598.857421875, 846.345458984375}, {715.7142333984375, 955.3599853515625}, {-919.9478759765625, 691.611328125}}},
646 TRange lowerRange, upperRange;
647 bruteMinT(reporter, quads[0], quads[1], &lowerRange, &upperRange);
648 bruteMinT(reporter, quads[2], quads[3], &lowerRange, &upperRange);
649 bruteMinT(reporter, quads[4], quads[5], &lowerRange, &upperRange);
653 The sorting problem happens when the inital tangents are not a true indicator of the curve direction
654 Nearly always, the initial tangents do give the right answer,
655 so the trick is to figure out when the initial tangent cannot be trusted.
656 If the convex hulls of both curves are in the same half plane, and not overlapping, sorting the
658 If the hulls overlap, and have the same general direction, then intersect the shorter end point ray
659 with the opposing curve, and see on which side of the shorter curve the opposing intersection lies.
660 Otherwise, if the control vector is extremely short, likely the point on curve must be computed
661 If moving the control point slightly can change the sign of the cross product, either answer could
663 We need to determine how short is extremely short. Move the control point a set percentage of
664 the largest length to determine how stable the curve is vis-a-vis the initial tangent.
667 static const SkDQuad extremeTests[][2] = {
669 {{{-708.0077926931004,-154.61669472244046},
670 {-707.9234268635319,-154.30459999551294},
671 {505.58447265625,-504.9130859375}}},
672 {{{-708.0077926931004,-154.61669472244046},
673 {-711.127526325141,-163.9446090624656},
674 {-32.39227294921875,-906.3277587890625}}},
676 {{{-708.0077926931004,-154.61669472244046},
677 {-708.2875337527566,-154.36676458635623},
678 {505.58447265625,-504.9130859375}}},
679 {{{-708.0077926931004,-154.61669472244046},
680 {-708.4111557216864,-154.5366642875255},
681 {-32.39227294921875,-906.3277587890625}}},
683 {{{-609.0230951752058,-267.5435593490574},
684 {-594.1120809906336,-136.08492475411555},
685 {505.58447265625,-504.9130859375}}},
686 {{{-609.0230951752058,-267.5435593490574},
687 {-693.7467719138988,-341.3259237831895},
688 {-32.39227294921875,-906.3277587890625}}}
690 {{{-708.0077926931004,-154.61669472244046},
691 {-707.9994640658723,-154.58588461064852},
692 {505.58447265625,-504.9130859375}}},
693 {{{-708.0077926931004,-154.61669472244046},
694 {-708.0239418990758,-154.6403553507124},
695 {-32.39227294921875,-906.3277587890625}}}
697 {{{-708.0077926931004,-154.61669472244046},
698 {-707.9993222215099,-154.55999389855003},
699 {68.88981098017803,296.9273945411635}}},
700 {{{-708.0077926931004,-154.61669472244046},
701 {-708.0509091919608,-154.64675214697067},
702 {-777.4871194247767,-995.1470120113145}}}
704 {{{-708.0077926931004,-154.61669472244046},
705 {-708.0060491116379,-154.60889321524968},
706 {229.97088707895057,-430.0569357467175}}},
707 {{{-708.0077926931004,-154.61669472244046},
708 {-708.013911296257,-154.6219143988058},
709 {138.13162892614037,-573.3689311737394}}}
711 {{{-543.2570545751013,-237.29243831075053},
712 {-452.4119186056987,-143.47223056267802},
713 {229.97088707895057,-430.0569357467175}}},
714 {{{-543.2570545751013,-237.29243831075053},
715 {-660.5330371214436,-362.0016148388},
716 {138.13162892614037,-573.3689311737394}}},
720 static double endCtrlRatio(const SkDQuad quad) {
721 SkDVector longEdge = quad[2] - quad[0];
722 double longLen = longEdge.length();
723 SkDVector shortEdge = quad[1] - quad[0];
724 double shortLen = shortEdge.length();
725 return longLen / shortLen;
728 static void computeMV(const SkDQuad& quad, const SkDVector& v, double m, SkDVector mV[2]) {
729 SkDPoint mPta = {quad[1].fX - m * v.fY, quad[1].fY + m * v.fX};
730 SkDPoint mPtb = {quad[1].fX + m * v.fY, quad[1].fY - m * v.fX};
731 mV[0] = mPta - quad[0];
732 mV[1] = mPtb - quad[0];
735 static double mDistance(skiatest::Reporter* reporter, bool agrees, const SkDQuad& q1,
740 // how close is the angle from inflecting in the opposite direction?
741 SkDVector v1 = q1[1] - q1[0];
742 SkDVector v2 = q2[1] - q2[0];
743 double dir = v1.crossCheck(v2);
744 REPORTER_ASSERT(reporter, dir != 0);
745 // solve for opposite direction displacement scale factor == m
746 // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x
747 // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1]
748 // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x)
749 // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x)
750 // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x
751 // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y)
752 // m = v1.cross(v2) / v1.dot(v2)
753 double div = v1.dot(v2);
754 REPORTER_ASSERT(reporter, div != 0);
755 double m = dir / div;
756 SkDVector mV1[2], mV2[2];
757 computeMV(q1, v1, m, mV1);
758 computeMV(q2, v2, m, mV2);
759 double dist1 = v1.length() * m;
760 double dist2 = v2.length() * m;
761 if (gPathOpsAngleIdeasVerbose) {
762 SkDebugf("%c r1=%1.9g r2=%1.9g m=%1.9g dist1=%1.9g dist2=%1.9g "
763 " dir%c 1a=%1.9g 1b=%1.9g 2a=%1.9g 2b=%1.9g\n", agrees ? 'T' : 'F',
764 endCtrlRatio(q1), endCtrlRatio(q2), m, dist1, dist2, dir > 0 ? '+' : '-',
765 mV1[0].crossCheck(v2), mV1[1].crossCheck(v2),
766 mV2[0].crossCheck(v1), mV2[1].crossCheck(v1));
769 bool use1 = fabs(dist1) < fabs(dist2);
770 if (gPathOpsAngleIdeasVerbose) {
771 SkDebugf("%c dist=%1.9g r=%1.9g\n", agrees ? 'T' : 'F', use1 ? dist1 : dist2,
772 use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
774 return fabs(use1 ? distEndRatio(dist1, q1) : distEndRatio(dist2, q2));
779 static void midPointAgrees(skiatest::Reporter* reporter, const SkDQuad& q1, const SkDQuad& q2,
781 SkDPoint mid1 = q1.ptAtT(0.5);
782 SkDVector m1 = mid1 - q1[0];
783 SkDPoint mid2 = q2.ptAtT(0.5);
784 SkDVector m2 = mid2 - q2[0];
785 REPORTER_ASSERT(reporter, ccw ? m1.crossCheck(m2) < 0 : m1.crossCheck(m2) > 0);
788 DEF_TEST(PathOpsAngleExtreme, reporter) {
789 if (!gPathOpsAngleIdeasVerbose) { // takes a while to run -- so exclude it by default
792 double maxR = SK_ScalarMax;
793 for (int index = 0; index < (int) SK_ARRAY_COUNT(extremeTests); ++index) {
794 const SkDQuad& quad1 = extremeTests[index][0];
795 const SkDQuad& quad2 = extremeTests[index][1];
796 if (gPathOpsAngleIdeasVerbose) {
797 SkDebugf("%s %d\n", __FUNCTION__, index);
799 REPORTER_ASSERT(reporter, quad1[0] == quad2[0]);
801 i.intersect(quad1, quad2);
802 REPORTER_ASSERT(reporter, i.used() == 1);
803 REPORTER_ASSERT(reporter, i.pt(0) == quad1[0]);
804 int overlap = quadHullsOverlap(reporter, quad1, quad2);
805 REPORTER_ASSERT(reporter, overlap >= 0);
806 SkDVector sweep[2], tweep[2];
807 setQuadHullSweep(quad1, sweep);
808 setQuadHullSweep(quad2, tweep);
809 double s0xt0 = sweep[0].crossCheck(tweep[0]);
810 REPORTER_ASSERT(reporter, s0xt0 != 0);
811 bool ccw = s0xt0 < 0;
812 bool agrees = bruteForceCheck(reporter, quad1, quad2, ccw);
813 maxR = SkTMin(maxR, mDistance(reporter, agrees, quad1, quad2));
817 midPointAgrees(reporter, quad1, quad2, !ccw);
822 // double vectors until t passes
824 q1[1].fX = quad1[0].fX * (1 - hiPass) + quad1[1].fX * hiPass;
825 q1[1].fY = quad1[0].fY * (1 - hiPass) + quad1[1].fY * hiPass;
826 q2[1].fX = quad2[0].fX * (1 - hiPass) + quad2[1].fX * hiPass;
827 q2[1].fY = quad2[0].fY * (1 - hiPass) + quad2[1].fY * hiPass;
828 agrees = bruteForceCheck(reporter, q1, q2, ccw);
829 maxR = SkTMin(maxR, mDistance(reporter, agrees, q1, q2));
833 midPointAgrees(reporter, quad1, quad2, !ccw);
837 // binary search to find minimum pass
838 double midTest = (loFail + hiPass) / 2;
839 double step = (hiPass - loFail) / 4;
840 while (step > FLT_EPSILON) {
841 q1[1].fX = quad1[0].fX * (1 - midTest) + quad1[1].fX * midTest;
842 q1[1].fY = quad1[0].fY * (1 - midTest) + quad1[1].fY * midTest;
843 q2[1].fX = quad2[0].fX * (1 - midTest) + quad2[1].fX * midTest;
844 q2[1].fY = quad2[0].fY * (1 - midTest) + quad2[1].fY * midTest;
845 agrees = bruteForceCheck(reporter, q1, q2, ccw);
846 maxR = SkTMin(maxR, mDistance(reporter, agrees, q1, q2));
848 midPointAgrees(reporter, quad1, quad2, !ccw);
850 midTest += agrees ? -step : step;
854 // DumpQ(q1, q2, 999);
857 if (gPathOpsAngleIdeasVerbose) {
858 SkDebugf("maxR=%1.9g\n", maxR);