DEF_TEST(PathOpsAngleFindQuadEpsilon, reporter) { if (gDisableAngleTests) { return; } SkRandom ran; int maxEpsilon = 0; double maxAngle = 0; for (int index = 0; index < 100000; ++index) { SkDLine line = {{{0, 0}, {ran.nextRangeF(0.0001f, 1000), ran.nextRangeF(0.0001f, 1000)}}}; float t = ran.nextRangeF(0.0001f, 1); SkDPoint dPt = line.ptAtT(t); float t2 = ran.nextRangeF(0.0001f, 1); SkDPoint qPt = line.ptAtT(t2); float t3 = ran.nextRangeF(0.0001f, 1); SkDPoint qPt2 = line.ptAtT(t3); qPt.fX += qPt2.fY; qPt.fY -= qPt2.fX; QuadPts q = {{line[0], dPt, qPt}}; SkDQuad quad; quad.debugSet(q.fPts); // binary search for maximum movement of quad[1] towards test that still has 1 intersection double moveT = 0.5f; double deltaT = moveT / 2; SkDPoint last; do { last = quad[1]; quad[1].fX = dPt.fX - line[1].fY * moveT; quad[1].fY = dPt.fY + line[1].fX * moveT; SkIntersections i; i.intersect(quad, line); REPORTER_ASSERT(reporter, i.used() > 0); if (i.used() == 1) { moveT += deltaT; } else { moveT -= deltaT; } deltaT /= 2; } while (last.asSkPoint() != quad[1].asSkPoint()); float p1 = SkDoubleToScalar(line[1].fX * last.fY); float p2 = SkDoubleToScalar(line[1].fY * last.fX); int p1Bits = SkFloatAs2sCompliment(p1); int p2Bits = SkFloatAs2sCompliment(p2); int epsilon = SkTAbs(p1Bits - p2Bits); if (maxEpsilon < epsilon) { SkDebugf("line={{0, 0}, {%1.7g, %1.7g}} t=%1.7g/%1.7g/%1.7g moveT=%1.7g" " pt={%1.7g, %1.7g} epsilon=%d\n", line[1].fX, line[1].fY, t, t2, t3, moveT, last.fX, last.fY, epsilon); maxEpsilon = epsilon; } double a1 = atan2(line[1].fY, line[1].fX); double a2 = atan2(last.fY, last.fX); double angle = fabs(a1 - a2); if (maxAngle < angle) { SkDebugf("line={{0, 0}, {%1.7g, %1.7g}} t=%1.7g/%1.7g/%1.7g moveT=%1.7g" " pt={%1.7g, %1.7g} angle=%1.7g\n", line[1].fX, line[1].fY, t, t2, t3, moveT, last.fX, last.fY, angle); maxAngle = angle; } } }
// from http://blog.gludion.com/2009/08/distance-to-quadratic-bezier-curve.html // (currently only used by testing) double SkDQuad::nearestT(const SkDPoint& pt) const { SkDVector pos = fPts[0] - pt; // search points P of bezier curve with PM.(dP / dt) = 0 // a calculus leads to a 3d degree equation : SkDVector A = fPts[1] - fPts[0]; SkDVector B = fPts[2] - fPts[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 = SkDCubic::RootsValidT(a, b, c, d, ts); double d0 = pt.distanceSquared(fPts[0]); double d2 = pt.distanceSquared(fPts[2]); double distMin = SkTMin(d0, d2); int bestIndex = -1; for (int index = 0; index < roots; ++index) { SkDPoint onQuad = ptAtT(ts[index]); 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 testLineIntersect(skiatest::Reporter* reporter, const SkDQuad& quad, const SkDLine& line, const double x, const double y) { char pathStr[1024]; sk_bzero(pathStr, sizeof(pathStr)); char* str = pathStr; str += sprintf(str, " path.moveTo(%1.9g, %1.9g);\n", quad[0].fX, quad[0].fY); str += sprintf(str, " path.quadTo(%1.9g, %1.9g, %1.9g, %1.9g);\n", quad[1].fX, quad[1].fY, quad[2].fX, quad[2].fY); str += sprintf(str, " path.moveTo(%1.9g, %1.9g);\n", line[0].fX, line[0].fY); str += sprintf(str, " path.lineTo(%1.9g, %1.9g);\n", line[1].fX, line[1].fY); SkIntersections intersections; bool flipped = false; int result = doIntersect(intersections, quad, line, flipped); bool found = false; for (int index = 0; index < result; ++index) { double quadT = intersections[0][index]; SkDPoint quadXY = quad.ptAtT(quadT); double lineT = intersections[1][index]; SkDPoint lineXY = line.ptAtT(lineT); if (quadXY.approximatelyEqual(lineXY)) { found = true; } } REPORTER_ASSERT(reporter, found); }
double SkDLine::nearPoint(const SkDPoint& xy) const { if (!AlmostBetweenUlps(fPts[0].fX, xy.fX, fPts[1].fX) || !AlmostBetweenUlps(fPts[0].fY, xy.fY, fPts[1].fY)) { return -1; } // project a perpendicular ray from the point to the line; find the T on the line SkDVector len = fPts[1] - fPts[0]; // the x/y magnitudes of the line double denom = len.fX * len.fX + len.fY * len.fY; // see DLine intersectRay SkDVector ab0 = xy - fPts[0]; double numer = len.fX * ab0.fX + ab0.fY * len.fY; if (!between(0, numer, denom)) { return -1; } double t = numer / denom; SkDPoint realPt = ptAtT(t); double dist = realPt.distance(xy); // OPTIMIZATION: can we compare against distSq instead ? // find the ordinal in the original line with the largest unsigned exponent double tiniest = SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); double largest = SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); largest = SkTMax(largest, -tiniest); if (!AlmostEqualUlps(largest, largest + dist)) { // is the dist within ULPS tolerance? return -1; } t = SkPinT(t); SkASSERT(between(0, t, 1)); return t; }
DEF_TEST(PathOpsAngleFindCrossEpsilon, reporter) { if (gDisableAngleTests) { return; } SkRandom ran; int maxEpsilon = 0; for (int index = 0; index < 10000000; ++index) { SkDLine line = {{{0, 0}, {ran.nextRangeF(0.0001f, 1000), ran.nextRangeF(0.0001f, 1000)}}}; for (int inner = 0; inner < 10; ++inner) { float t = ran.nextRangeF(0.0001f, 1); SkDPoint dPt = line.ptAtT(t); SkPoint pt = dPt.asSkPoint(); float xs[3] = { prev(pt.fX), pt.fX, next(pt.fX) }; float ys[3] = { prev(pt.fY), pt.fY, next(pt.fY) }; for (int xIdx = 0; xIdx < 3; ++xIdx) { for (int yIdx = 0; yIdx < 3; ++yIdx) { SkPoint test = { xs[xIdx], ys[yIdx] }; float p1 = SkDoubleToScalar(line[1].fX * test.fY); float p2 = SkDoubleToScalar(line[1].fY * test.fX); int p1Bits = SkFloatAs2sCompliment(p1); int p2Bits = SkFloatAs2sCompliment(p2); int epsilon = SkTAbs(p1Bits - p2Bits); if (maxEpsilon < epsilon) { SkDebugf("line={{0, 0}, {%1.7g, %1.7g}} t=%1.7g pt={%1.7g, %1.7g}" " epsilon=%d\n", line[1].fX, line[1].fY, t, test.fX, test.fY, epsilon); maxEpsilon = epsilon; } } } } } }
void SkOpSegment::dumpDPts() const { int count = SkPathOpsVerbToPoints(fVerb); SkDebugf("((SkOpSegment*) 0x%p) [%d] {{", this, debugID()); int index = 0; do { SkDPoint dPt = {fPts[index].fX, fPts[index].fY}; dPt.dump(); if (index != count) { SkDebugf(", "); } } while (++index <= count); SkDebugf("}}\n"); }
bool SkDLine::nearRay(const SkDPoint& xy) const { // project a perpendicular ray from the point to the line; find the T on the line SkDVector len = fPts[1] - fPts[0]; // the x/y magnitudes of the line double denom = len.fX * len.fX + len.fY * len.fY; // see DLine intersectRay SkDVector ab0 = xy - fPts[0]; double numer = len.fX * ab0.fX + ab0.fY * len.fY; double t = numer / denom; SkDPoint realPt = ptAtT(t); double dist = realPt.distance(xy); // OPTIMIZATION: can we compare against distSq instead ? // find the ordinal in the original line with the largest unsigned exponent double tiniest = SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); double largest = SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); largest = SkTMax(largest, -tiniest); return RoughlyEqualUlps(largest, largest + dist); // is the dist within ULPS tolerance? }
static void chopCompare(const SkConic chopped[2], const SkDConic dChopped[2]) { SkASSERT(roughly_equal(chopped[0].fW, dChopped[0].fWeight)); SkASSERT(roughly_equal(chopped[1].fW, dChopped[1].fWeight)); for (int cIndex = 0; cIndex < 2; ++cIndex) { for (int pIndex = 0; pIndex < 3; ++pIndex) { SkDPoint up; up.set(chopped[cIndex].fPts[pIndex]); SkASSERT(dChopped[cIndex].fPts[pIndex].approximatelyEqual(up)); } } #if DEBUG_VISUALIZE_CONICS dChopped[0].dump(); dChopped[1].dump(); #endif }
static void testOneOffs(skiatest::Reporter* reporter) { SkIntersections intersections; bool flipped = false; for (size_t index = 0; index < oneOffs_count; ++index) { const SkDQuad& quad = oneOffs[index].quad; const SkDLine& line = oneOffs[index].line; int result = doIntersect(intersections, quad, line, flipped); for (int inner = 0; inner < result; ++inner) { double quadT = intersections[0][inner]; SkDPoint quadXY = quad.xyAtT(quadT); double lineT = intersections[1][inner]; SkDPoint lineXY = line.xyAtT(lineT); REPORTER_ASSERT(reporter, quadXY.approximatelyEqual(lineXY)); } } }
static void pointFinder(const SkDQuad& q1, const SkDQuad& q2) { for (int index = 0; index < 3; ++index) { double t = q1.nearestT(q2[index]); SkDPoint onQuad = q1.ptAtT(t); SkDebugf("%s t=%1.9g (%1.9g,%1.9g) dist=%1.9g\n", __FUNCTION__, t, onQuad.fX, onQuad.fY, onQuad.distance(q2[index])); double left[3]; left[0] = ((const SkDLine&) q1[0]).isLeft(q2[index]); left[1] = ((const SkDLine&) q1[1]).isLeft(q2[index]); SkDLine diag = {{q1[0], q1[2]}}; left[2] = diag.isLeft(q2[index]); SkDebugf("%s left=(%d, %d, %d) inHull=%s\n", __FUNCTION__, floatSign(left[0]), floatSign(left[1]), floatSign(left[2]), q1.pointInHull(q2[index]) ? "true" : "false"); } SkDebugf("\n"); }
int SkIntersections::insert(double one, double two, const SkDPoint& pt) { if (fIsCoincident[0] == 3 && between(fT[0][0], one, fT[0][1])) { // For now, don't allow a mix of coincident and non-coincident intersections return -1; } SkASSERT(fUsed <= 1 || fT[0][0] <= fT[0][1]); int index; for (index = 0; index < fUsed; ++index) { double oldOne = fT[0][index]; double oldTwo = fT[1][index]; if (one == oldOne && two == oldTwo) { return -1; } if (more_roughly_equal(oldOne, one) && more_roughly_equal(oldTwo, two)) { if ((precisely_zero(one) && !precisely_zero(oldOne)) || (precisely_equal(one, 1) && !precisely_equal(oldOne, 1)) || (precisely_zero(two) && !precisely_zero(oldTwo)) || (precisely_equal(two, 1) && !precisely_equal(oldTwo, 1))) { SkASSERT(one >= 0 && one <= 1); SkASSERT(two >= 0 && two <= 1); fT[0][index] = one; fT[1][index] = two; fPt[index] = pt; } return -1; } #if ONE_OFF_DEBUG if (pt.roughlyEqual(fPt[index])) { SkDebugf("%s t=%1.9g pts roughly equal\n", __FUNCTION__, one); } #endif if (fT[0][index] > one) { break; } } if (fUsed >= fMax) { SkASSERT(0); // FIXME : this error, if it is to be handled at runtime in release, must // be propagated all the way back down to the caller, and return failure. fUsed = 0; return 0; } int remaining = fUsed - index; if (remaining > 0) { memmove(&fPt[index + 1], &fPt[index], sizeof(fPt[0]) * remaining); memmove(&fT[0][index + 1], &fT[0][index], sizeof(fT[0][0]) * remaining); memmove(&fT[1][index + 1], &fT[1][index], sizeof(fT[1][0]) * remaining); int clearMask = ~((1 << index) - 1); fIsCoincident[0] += fIsCoincident[0] & clearMask; fIsCoincident[1] += fIsCoincident[1] & clearMask; } fPt[index] = pt; SkASSERT(one >= 0 && one <= 1); SkASSERT(two >= 0 && two <= 1); fT[0][index] = one; fT[1][index] = two; ++fUsed; return index; }
DEF_TEST(PathOpsDPoint, reporter) { for (size_t index = 0; index < tests_count; ++index) { const SkDPoint& pt = tests[index]; SkASSERT(ValidPoint(pt)); SkDPoint p = pt; REPORTER_ASSERT(reporter, p == pt); REPORTER_ASSERT(reporter, !(pt != pt)); SkDVector v = p - pt; p += v; REPORTER_ASSERT(reporter, p == pt); p -= v; REPORTER_ASSERT(reporter, p == pt); REPORTER_ASSERT(reporter, p.approximatelyEqual(pt)); SkPoint sPt = pt.asSkPoint(); p.set(sPt); REPORTER_ASSERT(reporter, p == pt); REPORTER_ASSERT(reporter, p.approximatelyEqual(sPt)); REPORTER_ASSERT(reporter, p.roughlyEqual(pt)); p.fX = p.fY = 0; REPORTER_ASSERT(reporter, p.fX == 0 && p.fY == 0); REPORTER_ASSERT(reporter, p.approximatelyZero()); REPORTER_ASSERT(reporter, pt.distanceSquared(p) == pt.fX * pt.fX + pt.fY * pt.fY); REPORTER_ASSERT(reporter, approximately_equal(pt.distance(p), sqrt(pt.fX * pt.fX + pt.fY * pt.fY))); } }
void SkIntersections::cubicInsert(double one, double two, const SkDPoint& pt, const SkDCubic& cubic1, const SkDCubic& cubic2) { for (int index = 0; index < fUsed; ++index) { if (fT[0][index] == one) { double oldTwo = fT[1][index]; if (oldTwo == two) { return; } SkDPoint mid = cubic2.ptAtT((oldTwo + two) / 2); if (mid.approximatelyEqual(fPt[index])) { return; } } if (fT[1][index] == two) { SkDPoint mid = cubic1.ptAtT((fT[0][index] + two) / 2); if (mid.approximatelyEqual(fPt[index])) { return; } } } insert(one, two, pt); }
static void testOneOffs(skiatest::Reporter* reporter) { bool flipped = false; for (size_t index = 0; index < oneOffs_count; ++index) { const SkDConic& conic = oneOffs[index].conic; SkASSERT(ValidConic(conic)); const SkDLine& line = oneOffs[index].line; SkASSERT(ValidLine(line)); SkIntersections intersections; int result = doIntersect(intersections, conic, line, flipped); for (int inner = 0; inner < result; ++inner) { double conicT = intersections[0][inner]; SkDPoint conicXY = conic.ptAtT(conicT); double lineT = intersections[1][inner]; SkDPoint lineXY = line.ptAtT(lineT); if (!conicXY.approximatelyEqual(lineXY)) { conicXY.approximatelyEqual(lineXY); SkDebugf(""); } REPORTER_ASSERT(reporter, conicXY.approximatelyEqual(lineXY)); } } }
bool uniqueAnswer(double cubicT, const SkDPoint& pt) { for (int inner = 0; inner < fIntersections->used(); ++inner) { if (fIntersections->pt(inner) != pt) { continue; } double existingCubicT = (*fIntersections)[0][inner]; if (cubicT == existingCubicT) { return false; } // check if midway on cubic is also same point. If so, discard this double cubicMidT = (existingCubicT + cubicT) / 2; SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); if (cubicMidPt.approximatelyEqual(pt)) { return false; } } #if ONE_OFF_DEBUG SkDPoint cPt = fCubic.ptAtT(cubicT); SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, cPt.fX, cPt.fY); #endif return true; }
static void testOneOffs(skiatest::Reporter* reporter) { bool flipped = false; for (size_t index = 0; index < oneOffs_count; ++index) { const QuadPts& q = oneOffs[index].quad; SkDQuad quad; quad.debugSet(q.fPts); SkASSERT(ValidQuad(quad)); const SkDLine& line = oneOffs[index].line; SkASSERT(ValidLine(line)); SkIntersections intersections; int result = doIntersect(intersections, quad, line, flipped); for (int inner = 0; inner < result; ++inner) { double quadT = intersections[0][inner]; SkDPoint quadXY = quad.ptAtT(quadT); double lineT = intersections[1][inner]; SkDPoint lineXY = line.ptAtT(lineT); if (!quadXY.approximatelyEqual(lineXY)) { quadXY.approximatelyEqual(lineXY); SkDebugf(""); } REPORTER_ASSERT(reporter, quadXY.approximatelyEqual(lineXY)); } } }
int SkIntersections::closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, double* closestDist) const { int closest = -1; *closestDist = SK_ScalarMax; for (int index = 0; index < fUsed; ++index) { if (!between(rangeStart, fT[0][index], rangeEnd)) { continue; } const SkDPoint& iPt = fPt[index]; double dist = testPt.distanceSquared(iPt); if (*closestDist > dist) { *closestDist = dist; closest = index; } } return closest; }
bool SkOpAngle::endsIntersect(const SkOpAngle& rh) const { SkPath::Verb lVerb = fSegment->verb(); SkPath::Verb rVerb = rh.fSegment->verb(); int lPts = SkPathOpsVerbToPoints(lVerb); int rPts = SkPathOpsVerbToPoints(rVerb); SkDLine rays[] = {{{fCurvePart[0], rh.fCurvePart[rPts]}}, {{fCurvePart[0], fCurvePart[lPts]}}}; if (rays[0][1] == rays[1][1]) { return checkParallel(rh); } double smallTs[2] = {-1, -1}; bool limited[2] = {false, false}; for (int index = 0; index < 2; ++index) { const SkOpSegment& segment = index ? *rh.fSegment : *fSegment; SkIntersections i; (*CurveIntersectRay[index ? rPts : lPts])(segment.pts(), rays[index], &i); // SkASSERT(i.used() >= 1); // if (i.used() <= 1) { // continue; // } double tStart = segment.t(index ? rh.fStart : fStart); double tEnd = segment.t(index ? rh.fComputedEnd : fComputedEnd); bool testAscends = index ? rh.fStart < rh.fComputedEnd : fStart < fComputedEnd; double t = testAscends ? 0 : 1; for (int idx2 = 0; idx2 < i.used(); ++idx2) { double testT = i[0][idx2]; if (!approximately_between_orderable(tStart, testT, tEnd)) { continue; } if (approximately_equal_orderable(tStart, testT)) { continue; } smallTs[index] = t = testAscends ? SkTMax(t, testT) : SkTMin(t, testT); limited[index] = approximately_equal_orderable(t, tEnd); } } #if 0 if (smallTs[0] < 0 && smallTs[1] < 0) { // if neither ray intersects, do endpoint sort double m0xm1 = 0; if (lVerb == SkPath::kLine_Verb) { SkASSERT(rVerb != SkPath::kLine_Verb); SkDVector m0 = rays[1][1] - fCurvePart[0]; SkDPoint endPt; endPt.set(rh.fSegment->pts()[rh.fStart < rh.fEnd ? rPts : 0]); SkDVector m1 = endPt - fCurvePart[0]; m0xm1 = m0.crossCheck(m1); } if (rVerb == SkPath::kLine_Verb) { SkDPoint endPt; endPt.set(fSegment->pts()[fStart < fEnd ? lPts : 0]); SkDVector m0 = endPt - fCurvePart[0]; SkDVector m1 = rays[0][1] - fCurvePart[0]; m0xm1 = m0.crossCheck(m1); } if (m0xm1 != 0) { return m0xm1 < 0; } } #endif bool sRayLonger = false; SkDVector sCept = {0, 0}; double sCeptT = -1; int sIndex = -1; bool useIntersect = false; for (int index = 0; index < 2; ++index) { if (smallTs[index] < 0) { continue; } const SkOpSegment& segment = index ? *rh.fSegment : *fSegment; const SkDPoint& dPt = segment.dPtAtT(smallTs[index]); SkDVector cept = dPt - rays[index][0]; // If this point is on the curve, it should have been detected earlier by ordinary // curve intersection. This may be hard to determine in general, but for lines, // the point could be close to or equal to its end, but shouldn't be near the start. if ((index ? lPts : rPts) == 1) { SkDVector total = rays[index][1] - rays[index][0]; if (cept.lengthSquared() * 2 < total.lengthSquared()) { continue; } } SkDVector end = rays[index][1] - rays[index][0]; if (cept.fX * end.fX < 0 || cept.fY * end.fY < 0) { continue; } double rayDist = cept.length(); double endDist = end.length(); bool rayLonger = rayDist > endDist; if (limited[0] && limited[1] && rayLonger) { useIntersect = true; sRayLonger = rayLonger; sCept = cept; sCeptT = smallTs[index]; sIndex = index; break; } double delta = fabs(rayDist - endDist); double minX, minY, maxX, maxY; minX = minY = SK_ScalarInfinity; maxX = maxY = -SK_ScalarInfinity; const SkDCubic& curve = index ? rh.fCurvePart : fCurvePart; int ptCount = index ? rPts : lPts; for (int idx2 = 0; idx2 <= ptCount; ++idx2) { minX = SkTMin(minX, curve[idx2].fX); minY = SkTMin(minY, curve[idx2].fY); maxX = SkTMax(maxX, curve[idx2].fX); maxY = SkTMax(maxY, curve[idx2].fY); } double maxWidth = SkTMax(maxX - minX, maxY - minY); delta /= maxWidth; if (delta > 1e-4 && (useIntersect ^= true)) { // FIXME: move this magic number sRayLonger = rayLonger; sCept = cept; sCeptT = smallTs[index]; sIndex = index; } } if (useIntersect) { const SkDCubic& curve = sIndex ? rh.fCurvePart : fCurvePart; const SkOpSegment& segment = sIndex ? *rh.fSegment : *fSegment; double tStart = segment.t(sIndex ? rh.fStart : fStart); SkDVector mid = segment.dPtAtT(tStart + (sCeptT - tStart) / 2) - curve[0]; double septDir = mid.crossCheck(sCept); if (!septDir) { return checkParallel(rh); } return sRayLonger ^ (sIndex == 0) ^ (septDir < 0); } else { return checkParallel(rh); } }
static bool is_linear_inner(const SkDQuad& q1, double t1s, double t1e, const SkDQuad& q2, double t2s, double t2e, SkIntersections* i, bool* subDivide) { SkDQuad hull = q1.subDivide(t1s, t1e); SkDLine line = {{hull[2], hull[0]}}; const SkDLine* testLines[] = { &line, (const SkDLine*) &hull[0], (const SkDLine*) &hull[1] }; const size_t kTestCount = SK_ARRAY_COUNT(testLines); SkSTArray<kTestCount * 2, double, true> tsFound; for (size_t index = 0; index < kTestCount; ++index) { SkIntersections rootTs; rootTs.allowNear(false); int roots = rootTs.intersect(q2, *testLines[index]); for (int idx2 = 0; idx2 < roots; ++idx2) { double t = rootTs[0][idx2]; #ifdef SK_DEBUG SkDPoint qPt = q2.ptAtT(t); SkDPoint lPt = testLines[index]->ptAtT(rootTs[1][idx2]); SkASSERT(qPt.approximatelyEqual(lPt)); #endif if (approximately_negative(t - t2s) || approximately_positive(t - t2e)) { continue; } tsFound.push_back(rootTs[0][idx2]); } } int tCount = tsFound.count(); if (tCount <= 0) { return true; } double tMin, tMax; if (tCount == 1) { tMin = tMax = tsFound[0]; } else { SkASSERT(tCount > 1); SkTQSort<double>(tsFound.begin(), tsFound.end() - 1); tMin = tsFound[0]; tMax = tsFound[tsFound.count() - 1]; } SkDPoint end = q2.ptAtT(t2s); bool startInTriangle = hull.pointInHull(end); if (startInTriangle) { tMin = t2s; } end = q2.ptAtT(t2e); bool endInTriangle = hull.pointInHull(end); if (endInTriangle) { tMax = t2e; } int split = 0; SkDVector dxy1, dxy2; if (tMin != tMax || tCount > 2) { dxy2 = q2.dxdyAtT(tMin); for (int index = 1; index < tCount; ++index) { dxy1 = dxy2; dxy2 = q2.dxdyAtT(tsFound[index]); double dot = dxy1.dot(dxy2); if (dot < 0) { split = index - 1; break; } } } if (split == 0) { // there's one point if (add_intercept(q1, q2, tMin, tMax, i, subDivide)) { return true; } i->swap(); return is_linear_inner(q2, tMin, tMax, q1, t1s, t1e, i, subDivide); } // At this point, we have two ranges of t values -- treat each separately at the split bool result; if (add_intercept(q1, q2, tMin, tsFound[split - 1], i, subDivide)) { result = true; } else { i->swap(); result = is_linear_inner(q2, tMin, tsFound[split - 1], q1, t1s, t1e, i, subDivide); } if (add_intercept(q1, q2, tsFound[split], tMax, i, subDivide)) { result = true; } else { i->swap(); result |= is_linear_inner(q2, tsFound[split], tMax, q1, t1s, t1e, i, subDivide); } return result; }