static void oneOffTest1(size_t outer, size_t inner) { const Quadratic& quad1 = testSet[outer]; const Quadratic& quad2 = testSet[inner]; Intersections intersections2; intersect2(quad1, quad2, intersections2); if (intersections2.fUnsortable) { SkASSERT(0); return; } for (int pt = 0; pt < intersections2.used(); ++pt) { double tt1 = intersections2.fT[0][pt]; double tx1, ty1; xy_at_t(quad1, tt1, tx1, ty1); int pt2 = intersections2.fFlip ? intersections2.used() - pt - 1 : pt; double tt2 = intersections2.fT[1][pt2]; double tx2, ty2; xy_at_t(quad2, tt2, tx2, ty2); if (!AlmostEqualUlps(tx1, tx2)) { SkDebugf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)outer, (int)inner, tt1, tx1, ty1, tt2, tx2, ty2); SkASSERT(0); } if (!AlmostEqualUlps(ty1, ty2)) { SkDebugf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)outer, (int)inner, tt1, tx1, ty1, tt2, tx2, ty2); SkASSERT(0); } #if ONE_OFF_DEBUG SkDebugf("%s [%d][%d] t1=%1.9g (%1.9g, %1.9g) t2=%1.9g\n", __FUNCTION__, outer, inner, tt1, tx1, tx2, tt2); #endif } }
static void oneOffTest() { for (size_t outer = 0; outer < testSetCount - 1; ++outer) { for (size_t inner = outer + 1; inner < testSetCount; ++inner) { const Quadratic& quad1 = testSet[outer]; const Quadratic& quad2 = testSet[inner]; double tt1, tt2; Intersections intersections2; intersect2(quad1, quad2, intersections2); for (int pt = 0; pt < intersections2.used(); ++pt) { tt1 = intersections2.fT[0][pt]; double tx1, ty1; xy_at_t(quad1, tt1, tx1, ty1); int pt2 = intersections2.fFlip ? intersections2.used() - pt - 1 : pt; tt2 = intersections2.fT[1][pt2]; double tx2, ty2; xy_at_t(quad2, tt2, tx2, ty2); if (!approximately_equal(tx1, tx2)) { SkDebugf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); SkASSERT(0); } if (!approximately_equal(ty1, ty2)) { SkDebugf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); SkASSERT(0); } SkDebugf("%s [%d][%d] t1=%1.9g (%1.9g, %1.9g) t2=%1.9g\n", __FUNCTION__, outer, inner, tt1, tx1, tx2, tt2); } } } }
bool intersect(double minT1, double maxT1, double minT2, double maxT2) { Cubic sub1, sub2; // FIXME: carry last subdivide and reduceOrder result with cubic sub_divide(cubic1, minT1, maxT1, sub1); sub_divide(cubic2, minT2, maxT2, sub2); Intersections i; intersect2(sub1, sub2, i); if (i.used() == 0) { return false; } double x1, y1, x2, y2; t1 = minT1 + i.fT[0][0] * (maxT1 - minT1); t2 = minT2 + i.fT[1][0] * (maxT2 - minT2); xy_at_t(cubic1, t1, x1, y1); xy_at_t(cubic2, t2, x2, y2); if (AlmostEqualUlps(x1, x2) && AlmostEqualUlps(y1, y2)) { return true; } double half1 = (minT1 + maxT1) / 2; double half2 = (minT2 + maxT2) / 2; ++depth; bool result; if (depth & 1) { result = intersect(minT1, half1, minT2, maxT2) || intersect(half1, maxT1, minT2, maxT2) || intersect(minT1, maxT1, minT2, half2) || intersect(minT1, maxT1, half2, maxT2); } else { result = intersect(minT1, maxT1, minT2, half2) || intersect(minT1, maxT1, half2, maxT2) || intersect(minT1, half1, minT2, maxT2) || intersect(half1, maxT1, minT2, maxT2); } --depth; return result; }
double leftMostT(const Quadratic& quad, double startT, double endT) { double leftT; if (findExtrema(quad[0].x, quad[1].x, quad[2].x, &leftT) && startT <= leftT && leftT <= endT) { return leftT; } _Point startPt; xy_at_t(quad, startT, startPt.x, startPt.y); _Point endPt; xy_at_t(quad, endT, endPt.x, endPt.y); return startPt.x <= endPt.x ? startT : endT; }
void CubicIntersection_RandTest() { srand(0); const int tests = 10000000; for (int test = 0; test < tests; ++test) { Cubic cubic1, cubic2; for (int i = 0; i < 4; ++i) { cubic1[i].x = (double) rand() / RAND_MAX * 100; cubic1[i].y = (double) rand() / RAND_MAX * 100; cubic2[i].x = (double) rand() / RAND_MAX * 100; cubic2[i].y = (double) rand() / RAND_MAX * 100; } #if DEBUG_CRASH char str[1024]; sprintf(str, "{{%1.9g, %1.9g}, {%1.9g, %1.9g}, {%1.9g, %1.9g}, {%1.9g, %1.9g}},\n" "{{%1.9g, %1.9g}, {%1.9g, %1.9g}, {%1.9g, %1.9g}, {%1.9g, %1.9g}},\n", cubic1[0].x, cubic1[0].y, cubic1[1].x, cubic1[1].y, cubic1[2].x, cubic1[2].y, cubic1[3].x, cubic1[3].y, cubic2[0].x, cubic2[0].y, cubic2[1].x, cubic2[1].y, cubic2[2].x, cubic2[2].y, cubic2[3].x, cubic2[3].y); #endif _Rect rect1, rect2; rect1.setBounds(cubic1); rect2.setBounds(cubic2); bool boundsIntersect = rect1.left <= rect2.right && rect2.left <= rect2.right && rect1.top <= rect2.bottom && rect2.top <= rect1.bottom; if (test == -1) { SkDebugf("ready...\n"); } Intersections intersections2; bool newIntersects = intersect2(cubic1, cubic2, intersections2); if (!boundsIntersect && newIntersects) { SkDebugf("%s %d unexpected intersection boundsIntersect=%d " " newIntersects=%d\n%s %s\n", __FUNCTION__, test, boundsIntersect, newIntersects, __FUNCTION__, str); assert(0); } for (int pt = 0; pt < intersections2.used(); ++pt) { double tt1 = intersections2.fT[0][pt]; _Point xy1, xy2; xy_at_t(cubic1, tt1, xy1.x, xy1.y); int pt2 = intersections2.fFlip ? intersections2.used() - pt - 1 : pt; double tt2 = intersections2.fT[1][pt2]; xy_at_t(cubic2, tt2, xy2.x, xy2.y); #if 0 SkDebugf("%s t1=%1.9g (%1.9g, %1.9g) (%1.9g, %1.9g) t2=%1.9g\n", __FUNCTION__, tt1, xy1.x, xy1.y, xy2.x, xy2.y, tt2); #endif assert(xy1.approximatelyEqual(xy2)); } } }
static bool closeStart(const Cubic& cubic, int cubicIndex, Intersections& i, _Point& pt) { if (i.fT[cubicIndex][0] != 0 || i.fT[cubicIndex][1] > CLOSE_ENOUGH) { return false; } pt = xy_at_t(cubic, (i.fT[cubicIndex][0] + i.fT[cubicIndex][1]) / 2); return true; }
// from http://blog.gludion.com/2009/08/distance-to-quadratic-bezier-curve.html double nearestT(const Quadratic& quad, const _Point& pt) { _Vector pos = quad[0] - pt; // search points P of bezier curve with PM.(dP / dt) = 0 // a calculus leads to a 3d degree equation : _Vector A = quad[1] - quad[0]; _Vector B = quad[2] - quad[1]; B -= A; double a = B.dot(B); double b = 3 * A.dot(B); double c = 2 * A.dot(A) + pos.dot(B); double d = pos.dot(A); double ts[3]; int roots = cubicRootsValidT(a, b, c, d, ts); double d0 = pt.distanceSquared(quad[0]); double d2 = pt.distanceSquared(quad[2]); double distMin = SkTMin(d0, d2); int bestIndex = -1; for (int index = 0; index < roots; ++index) { _Point onQuad; xy_at_t(quad, ts[index], onQuad.x, onQuad.y); double dist = pt.distanceSquared(onQuad); if (distMin > dist) { distMin = dist; bestIndex = index; } } if (bestIndex >= 0) { return ts[bestIndex]; } return d0 < d2 ? 0 : 1; }
static void cubicTangent(const Cubic& cubic, double t, _Line& tangent, _Point& pt, _Point& dxy) { xy_at_t(cubic, t, tangent[0].x, tangent[0].y); pt = tangent[1] = tangent[0]; dxdy_at_t(cubic, t, dxy); tangent[0] -= dxy; tangent[1] += dxy; }
static bool closeEnd(const Cubic& cubic, int cubicIndex, Intersections& i, _Point& pt) { int last = i.used() - 1; if (i.fT[cubicIndex][last] != 1 || i.fT[cubicIndex][last - 1] < 1 - CLOSE_ENOUGH) { return false; } pt = xy_at_t(cubic, (i.fT[cubicIndex][last] + i.fT[cubicIndex][last - 1]) / 2); return true; }
// OPTIMIZE: avoid computing the unused half void xy_at_t(const Cubic& cubic, double t, double& x, double& y) { _Point xy = xy_at_t(cubic, t); if (&x) { x = xy.x; } if (&y) { y = xy.y; } }
void CubicIntersection_Test() { for (size_t index = firstCubicIntersectionTest; index < tests_count; ++index) { const Cubic& cubic1 = tests[index][0]; const Cubic& cubic2 = tests[index][1]; Cubic reduce1, reduce2; int order1 = reduceOrder(cubic1, reduce1, kReduceOrder_NoQuadraticsAllowed); int order2 = reduceOrder(cubic2, reduce2, kReduceOrder_NoQuadraticsAllowed); if (order1 < 4) { printf("%s [%d] cubic1 order=%d\n", __FUNCTION__, (int) index, order1); continue; } if (order2 < 4) { printf("%s [%d] cubic2 order=%d\n", __FUNCTION__, (int) index, order2); continue; } if (implicit_matches(reduce1, reduce2)) { printf("%s [%d] coincident\n", __FUNCTION__, (int) index); continue; } Intersections tIntersections; intersect(reduce1, reduce2, tIntersections); if (!tIntersections.intersected()) { printf("%s [%d] no intersection\n", __FUNCTION__, (int) index); continue; } for (int pt = 0; pt < tIntersections.used(); ++pt) { double tt1 = tIntersections.fT[0][pt]; double tx1, ty1; xy_at_t(cubic1, tt1, tx1, ty1); double tt2 = tIntersections.fT[1][pt]; double tx2, ty2; xy_at_t(cubic2, tt2, tx2, ty2); if (!AlmostEqualUlps(tx1, tx2)) { printf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } if (!AlmostEqualUlps(ty1, ty2)) { printf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } } } }
static void standardTestCases() { for (size_t index = firstQuadIntersectionTest; index < quadraticTests_count; ++index) { const Quadratic& quad1 = quadraticTests[index][0]; const Quadratic& quad2 = quadraticTests[index][1]; Quadratic reduce1, reduce2; int order1 = reduceOrder(quad1, reduce1); int order2 = reduceOrder(quad2, reduce2); if (order1 < 3) { printf("[%d] quad1 order=%d\n", (int) index, order1); } if (order2 < 3) { printf("[%d] quad2 order=%d\n", (int) index, order2); } if (order1 == 3 && order2 == 3) { Intersections intersections, intersections2; intersect(reduce1, reduce2, intersections); intersect2(reduce1, reduce2, intersections2); SkASSERT(intersections.used() == intersections2.used()); if (intersections.intersected()) { for (int pt = 0; pt < intersections.used(); ++pt) { double tt1 = intersections.fT[0][pt]; double tx1, ty1; xy_at_t(quad1, tt1, tx1, ty1); double tt2 = intersections.fT[1][pt]; double tx2, ty2; xy_at_t(quad2, tt2, tx2, ty2); if (!approximately_equal(tx1, tx2)) { printf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } if (!approximately_equal(ty1, ty2)) { printf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } tt1 = intersections2.fT[0][pt]; SkASSERT(approximately_equal(intersections.fT[0][pt], tt1)); tt2 = intersections2.fT[1][pt]; SkASSERT(approximately_equal(intersections.fT[1][pt], tt2)); } } } } }
int horizontalIntersect(double axisIntercept, double left, double right) { int roots = horizontalIntersect(axisIntercept); for (int index = 0; index < roots; ) { double x; xy_at_t(quad, intersections.fT[0][index], x, *(double*) NULL); double lineT = (x - left) / (right - left); if (lineIntersects(lineT, index, roots)) { ++index; } } return roots; }
int verticalIntersect(double axisIntercept, double top, double bottom) { int roots = verticalIntersect(axisIntercept); for (int index = 0; index < roots; ) { double y; xy_at_t(quad, intersections.fT[0][index], *(double*) NULL, y); double lineT = (y - top) / (bottom - top); if (lineIntersects(lineT, index, roots)) { ++index; } } return roots; }
static void oneOff(const Cubic& cubic1, const Cubic& cubic2) { SkTDArray<Quadratic> quads1; cubic_to_quadratics(cubic1, calcPrecision(cubic1), quads1); #if ONE_OFF_DEBUG for (int index = 0; index < quads1.count(); ++index) { const Quadratic& q = quads1[index]; SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", q[0].x, q[0].y, q[1].x, q[1].y, q[2].x, q[2].y); } SkDebugf("\n"); #endif SkTDArray<Quadratic> quads2; cubic_to_quadratics(cubic2, calcPrecision(cubic2), quads2); #if ONE_OFF_DEBUG for (int index = 0; index < quads2.count(); ++index) { const Quadratic& q = quads2[index]; SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", q[0].x, q[0].y, q[1].x, q[1].y, q[2].x, q[2].y); } SkDebugf("\n"); #endif Intersections intersections2; intersect2(cubic1, cubic2, intersections2); for (int pt = 0; pt < intersections2.used(); ++pt) { double tt1 = intersections2.fT[0][pt]; _Point xy1, xy2; xy_at_t(cubic1, tt1, xy1.x, xy1.y); int pt2 = intersections2.fFlip ? intersections2.used() - pt - 1 : pt; double tt2 = intersections2.fT[1][pt2]; xy_at_t(cubic2, tt2, xy2.x, xy2.y); #if ONE_OFF_DEBUG SkDebugf("%s t1=%1.9g (%1.9g, %1.9g) (%1.9g, %1.9g) t2=%1.9g\n", __FUNCTION__, tt1, xy1.x, xy1.y, xy2.x, xy2.y, tt2); #endif assert(xy1.approximatelyEqual(xy2)); } }
static double verticalIntersect(const Quadratic& quad, const _Point& pt) { Intersections intersections; LineQuadraticIntersections q(quad, *((_Line*) 0), intersections); int result = q.horizontalIntersect(pt.x); if (result == 0) { return -1; } assert(result == 1); double x, y; xy_at_t(quad, intersections.fT[0][0], x, y); if (approximately_equal(y, pt.y)) { return intersections.fT[0][0]; } return -1; }
void LineCubicIntersection_Test() { for (size_t index = firstLineCubicIntersectionTest; index < lineCubicTests_count; ++index) { const Cubic& cubic = lineCubicTests[index].cubic; const _Line& line = lineCubicTests[index].line; Cubic reduce1; _Line reduce2; int order1 = reduceOrder(cubic, reduce1, kReduceOrder_NoQuadraticsAllowed); int order2 = reduceOrder(line, reduce2); if (order1 < 4) { printf("[%d] cubic order=%d\n", (int) index, order1); } if (order2 < 2) { printf("[%d] line order=%d\n", (int) index, order2); } if (order1 == 4 && order2 == 2) { double range1[2], range2[2]; int roots = intersect(reduce1, reduce2, range1, range2); for (int pt = 0; pt < roots; ++pt) { double tt1 = range1[pt]; double tx1, ty1; xy_at_t(cubic, tt1, tx1, ty1); double tt2 = range2[pt]; double tx2, ty2; xy_at_t(line, tt2, tx2, ty2); if (!approximately_equal(tx1, tx2)) { printf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } if (!approximately_equal(ty1, ty2)) { printf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } } } } }
void LineIntersection_Test() { size_t index; for (index = firstLineIntersectionTest; index < tests_count; ++index) { const _Line& line1 = tests[index][0]; const _Line& line2 = tests[index][1]; double t1[2], t2[2]; int pts = intersect(line1, line2, t1, t2); if (!pts) { printf("%s [%zu] no intersection found\n", __FUNCTION__, index); } for (int i = 0; i < pts; ++i) { _Point result1, result2; xy_at_t(line1, t1[i], result1.x, result1.y); xy_at_t(line2, t2[i], result2.x, result2.y); if (!result1.approximatelyEqual(result2)) { if (pts == 1) { printf("%s [%zu] not equal\n", __FUNCTION__, index); } else { xy_at_t(line2, t2[i ^ 1], result2.x, result2.y); if (!result1.approximatelyEqual(result2)) { printf("%s [%zu] not equal\n", __FUNCTION__, index); } } } } } for (index = firstNoIntersectionTest; index < noIntersect_count; ++index) { const _Line& line1 = noIntersect[index][0]; const _Line& line2 = noIntersect[index][1]; double t1[2], t2[2]; int pts = intersect(line1, line2, t1, t2); if (pts) { printf("%s [%zu] no intersection expected\n", __FUNCTION__, index); } } }
// FIXME: after testing, make this static void computeDelta(const Cubic& c1, double t1, double scale1, const Cubic& c2, double t2, double scale2, double& delta1, double& delta2) { _Line tangent1, tangent2, line1, line2; _Point dxy1, dxy2; cubicTangent(c1, t1, line1, tangent1[0], dxy1); cubicTangent(c2, t2, line2, tangent2[0], dxy2); double range1[2], range2[2]; int found = intersect(line1, line2, range1, range2); if (found == 0) { range1[0] = 0.5; } else { SkASSERT(found == 1); } xy_at_t(line1, range1[0], tangent1[1].x, tangent1[1].y); #if SK_DEBUG if (found == 1) { xy_at_t(line2, range2[0], tangent2[1].x, tangent2[1].y); SkASSERT(tangent2[1].approximatelyEqual(tangent1[1])); } #endif tangent2[1] = tangent1[1]; delta1 = cubicDelta(dxy1, tangent1, scale1 / precisionUnit); delta2 = cubicDelta(dxy2, tangent2, scale2 / precisionUnit); }
int horizontalIntersect(const Quadratic& quad, double left, double right, double y, double tRange[2]) { Intersections i; LineQuadraticIntersections q(quad, *((_Line*) 0), i); int result = q.horizontalIntersect(y); int tCount = 0; for (int index = 0; index < result; ++index) { double x, y; xy_at_t(quad, i.fT[0][index], x, y); if (x < left || x > right) { continue; } tRange[tCount++] = i.fT[0][index]; } return tCount; }
void _Rect::setBounds(const Quadratic& quad) { set(quad[0]); add(quad[2]); double tValues[2]; int roots = 0; if (!between(quad[0].x, quad[1].x, quad[2].x)) { roots = findExtrema(quad[0].x, quad[1].x, quad[2].x, tValues); } if (!between(quad[0].y, quad[1].y, quad[2].y)) { roots += findExtrema(quad[0].y, quad[1].y, quad[2].y, &tValues[roots]); } for (int x = 0; x < roots; ++x) { _Point result; xy_at_t(quad, tValues[x], result.x, result.y); add(result); } }
static void pointFinder(const Quadratic& q1, const Quadratic& q2) { for (int index = 0; index < 3; ++index) { double t = nearestT(q1, q2[index]); _Point onQuad; xy_at_t(q1, t, onQuad.x, onQuad.y); SkDebugf("%s t=%1.9g (%1.9g,%1.9g) dist=%1.9g\n", __FUNCTION__, t, onQuad.x, onQuad.y, onQuad.distance(q2[index])); double left[3]; left[0] = is_left((const _Line&) q1[0], q2[index]); left[1] = is_left((const _Line&) q1[1], q2[index]); _Line diag = {q1[0], q1[2]}; left[2] = is_left(diag, q2[index]); SkDebugf("%s left=(%d, %d, %d) inHull=%s\n", __FUNCTION__, floatSign(left[0]), floatSign(left[1]), floatSign(left[2]), point_in_hull(q1, q2[index]) ? "true" : "false"); } SkDebugf("\n"); }
int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { addHorizontalEndPoints(left, right, axisIntercept); double rootVals[2]; int roots = horizontalIntersect(axisIntercept, rootVals); for (int index = 0; index < roots; ++index) { double x; double quadT = rootVals[index]; xy_at_t(quad, quadT, x, *(double*) NULL); double lineT = (x - left) / (right - left); if (pinTs(quadT, lineT)) { intersections.insert(quadT, lineT); } } if (flipped) { flip(); } return intersections.fUsed; }
int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { addVerticalEndPoints(top, bottom, axisIntercept); double rootVals[2]; int roots = verticalIntersect(axisIntercept, rootVals); for (int index = 0; index < roots; ++index) { double y; double quadT = rootVals[index]; xy_at_t(quad, quadT, *(double*) NULL, y); double lineT = (y - top) / (bottom - top); if (pinTs(quadT, lineT)) { intersections.insert(quadT, lineT); } } if (flipped) { flip(); } return intersections.fUsed; }
_Point top(const Quadratic& quad, double startT, double endT) { Quadratic sub; sub_divide(quad, startT, endT, sub); _Point topPt = sub[0]; if (topPt.y > sub[2].y || (topPt.y == sub[2].y && topPt.x > sub[2].x)) { topPt = sub[2]; } if (!between(sub[0].y, sub[1].y, sub[2].y)) { double extremeT; if (findExtrema(sub[0].y, sub[1].y, sub[2].y, &extremeT)) { extremeT = startT + (endT - startT) * extremeT; _Point test; xy_at_t(quad, extremeT, test.x, test.y); if (topPt.y > test.y || (topPt.y == test.y && topPt.x > test.x)) { topPt = test; } } } return topPt; }
_Point top(const Cubic& cubic, double startT, double endT) { Cubic sub; sub_divide(cubic, startT, endT, sub); _Point topPt = sub[0]; if (topPt.y > sub[3].y || (topPt.y == sub[3].y && topPt.x > sub[3].x)) { topPt = sub[3]; } double extremeTs[2]; if (!monotonic_in_y(sub)) { int roots = findExtrema(sub[0].y, sub[1].y, sub[2].y, sub[3].y, extremeTs); for (int index = 0; index < roots; ++index) { _Point mid; double t = startT + (endT - startT) * extremeTs[index]; xy_at_t(cubic, t, mid.x, mid.y); if (topPt.y > mid.y || (topPt.y == mid.y && topPt.x > mid.x)) { topPt = mid; } } } return topPt; }
bool intersect(double minT1, double maxT1, double minT2, double maxT2) { bool t1IsLine = maxT1 - minT1 <= quad1Divisions; bool t2IsLine = maxT2 - minT2 <= quad2Divisions; if (t1IsLine | t2IsLine) { return intersectAsLine(minT1, maxT1, minT2, maxT2, t1IsLine, t2IsLine); } Quadratic smaller, larger; // FIXME: carry last subdivide and reduceOrder result with quad sub_divide(quad1, minT1, maxT1, intersections.swapped() ? larger : smaller); sub_divide(quad2, minT2, maxT2, intersections.swapped() ? smaller : larger); double minT, maxT; if (!bezier_clip(smaller, larger, minT, maxT)) { if (approximately_equal(minT, maxT)) { double smallT, largeT; _Point q2pt, q1pt; if (intersections.swapped()) { largeT = interp(minT2, maxT2, minT); xy_at_t(quad2, largeT, q2pt.x, q2pt.y); xy_at_t(quad1, minT1, q1pt.x, q1pt.y); if (AlmostEqualUlps(q2pt.x, q1pt.x) && AlmostEqualUlps(q2pt.y, q1pt.y)) { smallT = minT1; } else { xy_at_t(quad1, maxT1, q1pt.x, q1pt.y); // FIXME: debug code assert(AlmostEqualUlps(q2pt.x, q1pt.x) && AlmostEqualUlps(q2pt.y, q1pt.y)); smallT = maxT1; } } else { smallT = interp(minT1, maxT1, minT); xy_at_t(quad1, smallT, q1pt.x, q1pt.y); xy_at_t(quad2, minT2, q2pt.x, q2pt.y); if (AlmostEqualUlps(q2pt.x, q1pt.x) && AlmostEqualUlps(q2pt.y, q1pt.y)) { largeT = minT2; } else { xy_at_t(quad2, maxT2, q2pt.x, q2pt.y); // FIXME: debug code assert(AlmostEqualUlps(q2pt.x, q1pt.x) && AlmostEqualUlps(q2pt.y, q1pt.y)); largeT = maxT2; } } intersections.add(smallT, largeT); return true; } return false; } int split; if (intersections.swapped()) { double newMinT1 = interp(minT1, maxT1, minT); double newMaxT1 = interp(minT1, maxT1, maxT); split = (newMaxT1 - newMinT1 > (maxT1 - minT1) * tClipLimit) << 1; #define VERBOSE 0 #if VERBOSE printf("%s d=%d s=%d new1=(%g,%g) old1=(%g,%g) split=%d\n", __FUNCTION__, depth, splits, newMinT1, newMaxT1, minT1, maxT1, split); #endif minT1 = newMinT1; maxT1 = newMaxT1; } else { double newMinT2 = interp(minT2, maxT2, minT); double newMaxT2 = interp(minT2, maxT2, maxT); split = newMaxT2 - newMinT2 > (maxT2 - minT2) * tClipLimit; #if VERBOSE printf("%s d=%d s=%d new2=(%g,%g) old2=(%g,%g) split=%d\n", __FUNCTION__, depth, splits, newMinT2, newMaxT2, minT2, maxT2, split); #endif minT2 = newMinT2; maxT2 = newMaxT2; } return chop(minT1, maxT1, minT2, maxT2, split); }
static void hackToFixPartialCoincidence(const Quadratic& q1, const Quadratic& q2, Intersections& i) { // look to see if non-coincident data basically has unsortable tangents // look to see if a point between non-coincident data is on the curve int cIndex; for (int uIndex = 0; uIndex < i.fUsed; ) { double bestDist1 = 1; double bestDist2 = 1; int closest1 = -1; int closest2 = -1; for (cIndex = 0; cIndex < i.fCoincidentUsed; ++cIndex) { double dist = fabs(i.fT[0][uIndex] - i.fCoincidentT[0][cIndex]); if (bestDist1 > dist) { bestDist1 = dist; closest1 = cIndex; } dist = fabs(i.fT[1][uIndex] - i.fCoincidentT[1][cIndex]); if (bestDist2 > dist) { bestDist2 = dist; closest2 = cIndex; } } _Line ends; _Point mid; double t1 = i.fT[0][uIndex]; xy_at_t(q1, t1, ends[0].x, ends[0].y); xy_at_t(q1, i.fCoincidentT[0][closest1], ends[1].x, ends[1].y); double midT = (t1 + i.fCoincidentT[0][closest1]) / 2; xy_at_t(q1, midT, mid.x, mid.y); LineParameters params; params.lineEndPoints(ends); double midDist = params.pointDistance(mid); // Note that we prefer to always measure t error, which does not scale, // instead of point error, which is scale dependent. FIXME if (!approximately_zero(midDist)) { ++uIndex; continue; } double t2 = i.fT[1][uIndex]; xy_at_t(q2, t2, ends[0].x, ends[0].y); xy_at_t(q2, i.fCoincidentT[1][closest2], ends[1].x, ends[1].y); midT = (t2 + i.fCoincidentT[1][closest2]) / 2; xy_at_t(q2, midT, mid.x, mid.y); params.lineEndPoints(ends); midDist = params.pointDistance(mid); if (!approximately_zero(midDist)) { ++uIndex; continue; } // if both midpoints are close to the line, lengthen coincident span int cEnd = closest1 ^ 1; // assume coincidence always travels in pairs if (!between(i.fCoincidentT[0][cEnd], t1, i.fCoincidentT[0][closest1])) { i.fCoincidentT[0][closest1] = t1; } cEnd = closest2 ^ 1; if (!between(i.fCoincidentT[0][cEnd], t2, i.fCoincidentT[0][closest2])) { i.fCoincidentT[0][closest2] = t2; } int remaining = --i.fUsed - uIndex; if (remaining > 0) { memmove(&i.fT[0][uIndex], &i.fT[0][uIndex + 1], sizeof(i.fT[0][0]) * remaining); memmove(&i.fT[1][uIndex], &i.fT[1][uIndex + 1], sizeof(i.fT[1][0]) * remaining); } } // if coincident data is subjectively a tiny span, replace it with a single point for (cIndex = 0; cIndex < i.fCoincidentUsed; ) { double start1 = i.fCoincidentT[0][cIndex]; double end1 = i.fCoincidentT[0][cIndex + 1]; _Line ends1; xy_at_t(q1, start1, ends1[0].x, ends1[0].y); xy_at_t(q1, end1, ends1[1].x, ends1[1].y); if (!AlmostEqualUlps(ends1[0].x, ends1[1].x) || AlmostEqualUlps(ends1[0].y, ends1[1].y)) { cIndex += 2; continue; } double start2 = i.fCoincidentT[1][cIndex]; double end2 = i.fCoincidentT[1][cIndex + 1]; _Line ends2; xy_at_t(q2, start2, ends2[0].x, ends2[0].y); xy_at_t(q2, end2, ends2[1].x, ends2[1].y); // again, approximately should be used with T values, not points FIXME if (!AlmostEqualUlps(ends2[0].x, ends2[1].x) || AlmostEqualUlps(ends2[0].y, ends2[1].y)) { cIndex += 2; continue; } if (approximately_less_than_zero(start1) || approximately_less_than_zero(end1)) { start1 = 0; } else if (approximately_greater_than_one(start1) || approximately_greater_than_one(end1)) { start1 = 1; } else { start1 = (start1 + end1) / 2; } if (approximately_less_than_zero(start2) || approximately_less_than_zero(end2)) { start2 = 0; } else if (approximately_greater_than_one(start2) || approximately_greater_than_one(end2)) { start2 = 1; } else { start2 = (start2 + end2) / 2; } i.insert(start1, start2); i.fCoincidentUsed -= 2; int remaining = i.fCoincidentUsed - cIndex; if (remaining > 0) { memmove(&i.fCoincidentT[0][cIndex], &i.fCoincidentT[0][cIndex + 2], sizeof(i.fCoincidentT[0][0]) * remaining); memmove(&i.fCoincidentT[1][cIndex], &i.fCoincidentT[1][cIndex + 2], sizeof(i.fCoincidentT[1][0]) * remaining); } } }
bool intersectAsLine(double minT1, double maxT1, double minT2, double maxT2, bool treat1AsLine, bool treat2AsLine) { _Line line1, line2; if (intersections.swapped()) { std::swap(treat1AsLine, treat2AsLine); std::swap(minT1, minT2); std::swap(maxT1, maxT2); } if (coinMinT1 >= 0) { bool earlyExit; if ((earlyExit = coinMaxT1 == minT1)) { coinMaxT1 = maxT1; } if (coinMaxT2 == minT2) { coinMaxT2 = maxT2; return true; } if (earlyExit) { return true; } coinMinT1 = -1; } // do line/quadratic or even line/line intersection instead if (treat1AsLine) { xy_at_t(quad1, minT1, line1[0].x, line1[0].y); xy_at_t(quad1, maxT1, line1[1].x, line1[1].y); } if (treat2AsLine) { xy_at_t(quad2, minT2, line2[0].x, line2[0].y); xy_at_t(quad2, maxT2, line2[1].x, line2[1].y); } int pts; double smallT1, largeT1, smallT2, largeT2; if (treat1AsLine & treat2AsLine) { double t1[2], t2[2]; pts = ::intersect(line1, line2, t1, t2); if (pts == 2) { smallT1 = interp(minT1, maxT1, t1[0]); largeT1 = interp(minT2, maxT2, t2[0]); smallT2 = interp(minT1, maxT1, t1[1]); largeT2 = interp(minT2, maxT2, t2[1]); intersections.addCoincident(smallT1, smallT2, largeT1, largeT2); } else { smallT1 = interp(minT1, maxT1, t1[0]); largeT1 = interp(minT2, maxT2, t2[0]); intersections.add(smallT1, largeT1); } } else { Intersections lq; pts = ::intersect(treat1AsLine ? quad2 : quad1, treat1AsLine ? line1 : line2, lq); if (pts == 2) { // if the line and edge are coincident treat differently _Point midQuad, midLine; double midQuadT = (lq.fT[0][0] + lq.fT[0][1]) / 2; xy_at_t(treat1AsLine ? quad2 : quad1, midQuadT, midQuad.x, midQuad.y); double lineT = t_at(treat1AsLine ? line1 : line2, midQuad); xy_at_t(treat1AsLine ? line1 : line2, lineT, midLine.x, midLine.y); if (AlmostEqualUlps(midQuad.x, midLine.x) && AlmostEqualUlps(midQuad.y, midLine.y)) { smallT1 = lq.fT[0][0]; largeT1 = lq.fT[1][0]; smallT2 = lq.fT[0][1]; largeT2 = lq.fT[1][1]; if (treat2AsLine) { smallT1 = interp(minT1, maxT1, smallT1); smallT2 = interp(minT1, maxT1, smallT2); } else { largeT1 = interp(minT2, maxT2, largeT1); largeT2 = interp(minT2, maxT2, largeT2); } intersections.addCoincident(smallT1, smallT2, largeT1, largeT2); goto setCoinMinMax; } } for (int index = 0; index < pts; ++index) { smallT1 = lq.fT[0][index]; largeT1 = lq.fT[1][index]; if (treat2AsLine) { smallT1 = interp(minT1, maxT1, smallT1); } else { largeT1 = interp(minT2, maxT2, largeT1); } intersections.add(smallT1, largeT1); } } if (pts > 0) { setCoinMinMax: coinMinT1 = minT1; coinMaxT1 = maxT1; coinMinT2 = minT2; coinMaxT2 = maxT2; } return pts > 0; }
// this flavor centers potential intersections recursively. In contrast, '2' may inadvertently // chase intersections near quadratic ends, requiring odd hacks to find them. static bool intersect3(const Cubic& cubic1, double t1s, double t1e, const Cubic& cubic2, double t2s, double t2e, double precisionScale, Intersections& i) { i.upDepth(); bool result = false; Cubic c1, c2; sub_divide(cubic1, t1s, t1e, c1); sub_divide(cubic2, t2s, t2e, c2); SkTDArray<double> ts1; // OPTIMIZE: if c1 == c2, call once (happens when detecting self-intersection) cubic_to_quadratics(c1, calcPrecision(c1) * precisionScale, ts1); SkTDArray<double> ts2; cubic_to_quadratics(c2, calcPrecision(c2) * precisionScale, ts2); double t1Start = t1s; int ts1Count = ts1.count(); for (int i1 = 0; i1 <= ts1Count; ++i1) { const double tEnd1 = i1 < ts1Count ? ts1[i1] : 1; const double t1 = t1s + (t1e - t1s) * tEnd1; Quadratic s1; int o1 = quadPart(cubic1, t1Start, t1, s1); double t2Start = t2s; int ts2Count = ts2.count(); for (int i2 = 0; i2 <= ts2Count; ++i2) { const double tEnd2 = i2 < ts2Count ? ts2[i2] : 1; const double t2 = t2s + (t2e - t2s) * tEnd2; if (cubic1 == cubic2 && t1Start >= t2Start) { t2Start = t2; continue; } Quadratic s2; int o2 = quadPart(cubic2, t2Start, t2, s2); #if ONE_OFF_DEBUG char tab[] = " "; if (tLimits1[0][0] >= t1Start && tLimits1[0][1] <= t1 && tLimits1[1][0] >= t2Start && tLimits1[1][1] <= t2) { Cubic cSub1, cSub2; sub_divide(cubic1, t1Start, t1, cSub1); sub_divide(cubic2, t2Start, t2, cSub2); SkDebugf("%.*s %s t1=(%1.9g,%1.9g) t2=(%1.9g,%1.9g)", i.depth()*2, tab, __FUNCTION__, t1Start, t1, t2Start, t2); Intersections xlocals; intersectWithOrder(s1, o1, s2, o2, xlocals); SkDebugf(" xlocals.fUsed=%d\n", xlocals.used()); } #endif Intersections locals; intersectWithOrder(s1, o1, s2, o2, locals); double coStart[2] = { -1 }; _Point coPoint; int tCount = locals.used(); for (int tIdx = 0; tIdx < tCount; ++tIdx) { double to1 = t1Start + (t1 - t1Start) * locals.fT[0][tIdx]; double to2 = t2Start + (t2 - t2Start) * locals.fT[1][tIdx]; // if the computed t is not sufficiently precise, iterate _Point p1 = xy_at_t(cubic1, to1); _Point p2 = xy_at_t(cubic2, to2); if (p1.approximatelyEqual(p2)) { if (locals.fIsCoincident[0] & 1 << tIdx) { if (coStart[0] < 0) { coStart[0] = to1; coStart[1] = to2; coPoint = p1; } else { i.insertCoincidentPair(coStart[0], to1, coStart[1], to2, coPoint, p1); coStart[0] = -1; } result = true; } else if (cubic1 != cubic2 || !approximately_equal(to1, to2)) { if (i.swapped()) { // FIXME: insert should respect swap i.insert(to2, to1, p1); } else { i.insert(to1, to2, p1); } result = true; } } else { double offset = precisionScale / 16; // FIME: const is arbitrary -- test & refine #if 1 double c1Bottom = tIdx == 0 ? 0 : (t1Start + (t1 - t1Start) * locals.fT[0][tIdx - 1] + to1) / 2; double c1Min = SkTMax(c1Bottom, to1 - offset); double c1Top = tIdx == tCount - 1 ? 1 : (t1Start + (t1 - t1Start) * locals.fT[0][tIdx + 1] + to1) / 2; double c1Max = SkTMin(c1Top, to1 + offset); double c2Min = SkTMax(0., to2 - offset); double c2Max = SkTMin(1., to2 + offset); #if ONE_OFF_DEBUG SkDebugf("%.*s %s 1 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab, __FUNCTION__, c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max, to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset, c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max, to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset); SkDebugf("%.*s %s 1 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", i.depth()*2, tab, __FUNCTION__, c1Bottom, c1Top, 0., 1., to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); SkDebugf("%.*s %s 1 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min, c1Max, c2Min, c2Max); #endif intersect3(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); #if ONE_OFF_DEBUG SkDebugf("%.*s %s 1 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__, i.used(), i.used() > 0 ? i.fT[0][i.used() - 1] : -1); #endif if (tCount > 1) { c1Min = SkTMax(0., to1 - offset); c1Max = SkTMin(1., to1 + offset); double c2Bottom = tIdx == 0 ? to2 : (t2Start + (t2 - t2Start) * locals.fT[1][tIdx - 1] + to2) / 2; double c2Top = tIdx == tCount - 1 ? to2 : (t2Start + (t2 - t2Start) * locals.fT[1][tIdx + 1] + to2) / 2; if (c2Bottom > c2Top) { SkTSwap(c2Bottom, c2Top); } if (c2Bottom == to2) { c2Bottom = 0; } if (c2Top == to2) { c2Top = 1; } c2Min = SkTMax(c2Bottom, to2 - offset); c2Max = SkTMin(c2Top, to2 + offset); #if ONE_OFF_DEBUG SkDebugf("%.*s %s 2 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab, __FUNCTION__, c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max, to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset, c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max, to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset); SkDebugf("%.*s %s 2 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top, to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); SkDebugf("%.*s %s 2 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min, c1Max, c2Min, c2Max); #endif intersect3(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); #if ONE_OFF_DEBUG SkDebugf("%.*s %s 2 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__, i.used(), i.used() > 0 ? i.fT[0][i.used() - 1] : -1); #endif c1Min = SkTMax(c1Bottom, to1 - offset); c1Max = SkTMin(c1Top, to1 + offset); #if ONE_OFF_DEBUG SkDebugf("%.*s %s 3 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab, __FUNCTION__, c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max, to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset, c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max, to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset); SkDebugf("%.*s %s 3 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top, to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); SkDebugf("%.*s %s 3 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min, c1Max, c2Min, c2Max); #endif intersect3(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); #if ONE_OFF_DEBUG SkDebugf("%.*s %s 3 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__, i.used(), i.used() > 0 ? i.fT[0][i.used() - 1] : -1); #endif } #else double c1Bottom = tIdx == 0 ? 0 : (t1Start + (t1 - t1Start) * locals.fT[0][tIdx - 1] + to1) / 2; double c1Min = SkTMax(c1Bottom, to1 - offset); double c1Top = tIdx == tCount - 1 ? 1 : (t1Start + (t1 - t1Start) * locals.fT[0][tIdx + 1] + to1) / 2; double c1Max = SkTMin(c1Top, to1 + offset); double c2Bottom = tIdx == 0 ? to2 : (t2Start + (t2 - t2Start) * locals.fT[1][tIdx - 1] + to2) / 2; double c2Top = tIdx == tCount - 1 ? to2 : (t2Start + (t2 - t2Start) * locals.fT[1][tIdx + 1] + to2) / 2; if (c2Bottom > c2Top) { SkTSwap(c2Bottom, c2Top); } if (c2Bottom == to2) { c2Bottom = 0; } if (c2Top == to2) { c2Top = 1; } double c2Min = SkTMax(c2Bottom, to2 - offset); double c2Max = SkTMin(c2Top, to2 + offset); #if ONE_OFF_DEBUG SkDebugf("%s contains1=%d/%d contains2=%d/%d\n", __FUNCTION__, c1Min <= 0.210357794 && 0.210357794 <= c1Max && c2Min <= 0.223476406 && 0.223476406 <= c2Max, to1 - offset <= 0.210357794 && 0.210357794 <= to1 + offset && to2 - offset <= 0.223476406 && 0.223476406 <= to2 + offset, c1Min <= 0.211324707 && 0.211324707 <= c1Max && c2Min <= 0.211327209 && 0.211327209 <= c2Max, to1 - offset <= 0.211324707 && 0.211324707 <= to1 + offset && to2 - offset <= 0.211327209 && 0.211327209 <= to2 + offset); SkDebugf("%s c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", __FUNCTION__, c1Bottom, c1Top, c2Bottom, c2Top, to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); SkDebugf("%s to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" " c2Max=%1.9g\n", __FUNCTION__, to1, to2, c1Min, c1Max, c2Min, c2Max); #endif #endif intersect3(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); // TODO: if no intersection is found, either quadratics intersected where // cubics did not, or the intersection was missed. In the former case, expect // the quadratics to be nearly parallel at the point of intersection, and check // for that. } } SkASSERT(coStart[0] == -1); t2Start = t2; } t1Start = t1; } i.downDepth(); return result; }