static bool checkParallel(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2) { SkDVector sweep[2], tweep[2]; setQuadHullSweep(quad1, sweep); setQuadHullSweep(quad2, tweep); // if the ctrl tangents are not nearly parallel, use them // solve for opposite direction displacement scale factor == m // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1] // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x) // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x) // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y) // m = v1.cross(v2) / v1.dot(v2) double s0dt0 = sweep[0].dot(tweep[0]); REPORTER_ASSERT(reporter, s0dt0 != 0); double s0xt0 = sweep[0].crossCheck(tweep[0]); double m = s0xt0 / s0dt0; double sDist = sweep[0].length() * m; double tDist = tweep[0].length() * m; bool useS = fabs(sDist) < fabs(tDist); double mFactor = fabs(useS ? distEndRatio(sDist, quad1) : distEndRatio(tDist, quad2)); if (mFactor < 5000) { // empirically found limit return s0xt0 < 0; } SkDVector m0 = quad1.ptAtT(0.5) - quad1[0]; SkDVector m1 = quad2.ptAtT(0.5) - quad2[0]; return m0.crossCheck(m1) < 0; }
// returns false if there's more than one intercept or the intercept doesn't match the point // returns true if the intercept was successfully added or if the // original quads need to be subdivided static bool add_intercept(const SkDQuad& q1, const SkDQuad& q2, double tMin, double tMax, SkIntersections* i, bool* subDivide) { double tMid = (tMin + tMax) / 2; SkDPoint mid = q2.ptAtT(tMid); SkDLine line; line[0] = line[1] = mid; SkDVector dxdy = q2.dxdyAtT(tMid); line[0] -= dxdy; line[1] += dxdy; SkIntersections rootTs; rootTs.allowNear(false); int roots = rootTs.intersect(q1, line); if (roots == 0) { if (subDivide) { *subDivide = true; } return true; } if (roots == 2) { return false; } SkDPoint pt2 = q1.ptAtT(rootTs[0][0]); if (!pt2.approximatelyEqualHalf(mid)) { return false; } i->insertSwap(rootTs[0][0], tMid, pt2); return true; }
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); }
/* returns -1 if overlaps 0 if no overlap cw 1 if no overlap ccw */ static int quadHullsOverlap(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2) { SkDVector sweep[2], tweep[2]; setQuadHullSweep(quad1, sweep); setQuadHullSweep(quad2, tweep); double s0xs1 = sweep[0].crossCheck(sweep[1]); double s0xt0 = sweep[0].crossCheck(tweep[0]); double s1xt0 = sweep[1].crossCheck(tweep[0]); bool tBetweenS = s0xs1 > 0 ? s0xt0 > 0 && s1xt0 < 0 : s0xt0 < 0 && s1xt0 > 0; double s0xt1 = sweep[0].crossCheck(tweep[1]); double s1xt1 = sweep[1].crossCheck(tweep[1]); tBetweenS |= s0xs1 > 0 ? s0xt1 > 0 && s1xt1 < 0 : s0xt1 < 0 && s1xt1 > 0; double t0xt1 = tweep[0].crossCheck(tweep[1]); if (tBetweenS) { return -1; } if ((s0xt0 == 0 && s1xt1 == 0) || (s1xt0 == 0 && s0xt1 == 0)) { // s0 to s1 equals t0 to t1 return -1; } bool sBetweenT = t0xt1 > 0 ? s0xt0 < 0 && s0xt1 > 0 : s0xt0 > 0 && s0xt1 < 0; sBetweenT |= t0xt1 > 0 ? s1xt0 < 0 && s1xt1 > 0 : s1xt0 > 0 && s1xt1 < 0; if (sBetweenT) { return -1; } // if all of the sweeps are in the same half plane, then the order of any pair is enough if (s0xt0 >= 0 && s0xt1 >= 0 && s1xt0 >= 0 && s1xt1 >= 0) { return 0; } if (s0xt0 <= 0 && s0xt1 <= 0 && s1xt0 <= 0 && s1xt1 <= 0) { return 1; } // if the outside sweeps are greater than 180 degress: // first assume the inital tangents are the ordering // if the midpoint direction matches the inital order, that is enough SkDVector m0 = quad1.ptAtT(0.5) - quad1[0]; SkDVector m1 = quad2.ptAtT(0.5) - quad2[0]; double m0xm1 = m0.crossCheck(m1); if (s0xt0 > 0 && m0xm1 > 0) { return 0; } if (s0xt0 < 0 && m0xm1 < 0) { return 1; } REPORTER_ASSERT(reporter, s0xt0 != 0); return checkParallel(reporter, quad1, quad2); }
void SkGlyphCache::AddQuad(const SkPoint pts[2], SkScalar axis, bool yAxis, SkGlyph::Intercept* intercept) { SkDQuad quad; quad.set(pts); double roots[2]; int count = yAxis ? quad.verticalIntersect(axis, roots) : quad.horizontalIntersect(axis, roots); while (--count >= 0) { SkPoint pt = quad.ptAtT(roots[count]).asSkPoint(); AddInterval(*(&pt.fX + yAxis), intercept); } }
void SkDRect::setBounds(const SkDQuad& quad) { set(quad[0]); add(quad[2]); double tValues[2]; int roots = 0; if (!between(quad[0].fX, quad[1].fX, quad[2].fX)) { roots = SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, tValues); } if (!between(quad[0].fY, quad[1].fY, quad[2].fY)) { roots += SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValues[roots]); } for (int x = 0; x < roots; ++x) { add(quad.ptAtT(tValues[x])); } }
DEF_TEST(PathOpsQuadLineIntersection, reporter) { for (size_t index = 0; index < lineQuadTests_count; ++index) { int iIndex = static_cast<int>(index); const QuadPts& q = lineQuadTests[index].quad; SkDQuad quad; quad.debugSet(q.fPts); SkASSERT(ValidQuad(quad)); const SkDLine& line = lineQuadTests[index].line; SkASSERT(ValidLine(line)); SkReduceOrder reducer1, reducer2; int order1 = reducer1.reduce(quad); int order2 = reducer2.reduce(line); if (order1 < 3) { SkDebugf("%s [%d] quad order=%d\n", __FUNCTION__, iIndex, order1); REPORTER_ASSERT(reporter, 0); } if (order2 < 2) { SkDebugf("%s [%d] line order=%d\n", __FUNCTION__, iIndex, order2); REPORTER_ASSERT(reporter, 0); } SkIntersections intersections; bool flipped = false; int result = doIntersect(intersections, quad, line, flipped); REPORTER_ASSERT(reporter, result == lineQuadTests[index].result); if (intersections.used() <= 0) { continue; } for (int pt = 0; pt < result; ++pt) { double tt1 = intersections[0][pt]; REPORTER_ASSERT(reporter, tt1 >= 0 && tt1 <= 1); SkDPoint t1 = quad.ptAtT(tt1); double tt2 = intersections[1][pt]; REPORTER_ASSERT(reporter, tt2 >= 0 && tt2 <= 1); SkDPoint t2 = line.ptAtT(tt2); if (!t1.approximatelyEqual(t2)) { SkDebugf("%s [%d,%d] x!= t1=%1.9g (%1.9g,%1.9g) t2=%1.9g (%1.9g,%1.9g)\n", __FUNCTION__, iIndex, pt, tt1, t1.fX, t1.fY, tt2, t2.fX, t2.fY); REPORTER_ASSERT(reporter, 0); } if (!t1.approximatelyEqual(lineQuadTests[index].expected[0]) && (lineQuadTests[index].result == 1 || !t1.approximatelyEqual(lineQuadTests[index].expected[1]))) { SkDebugf("%s t1=(%1.9g,%1.9g)\n", __FUNCTION__, t1.fX, t1.fY); REPORTER_ASSERT(reporter, 0); } } } }
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"); }
// find a point on a quad by choosing a t from 0 to 1 // create a vertical span above and below the point // verify that intersecting the vertical span and the quad returns t // verify that a vertical span starting at quad[0] intersects at t=0 // verify that a vertical span starting at quad[2] intersects at t=1 static void testQuadLineIntersectMain(PathOpsThreadState* data) { PathOpsThreadState& state = *data; REPORTER_ASSERT(state.fReporter, data); int ax = state.fA & 0x03; int ay = state.fA >> 2; int bx = state.fB & 0x03; int by = state.fB >> 2; int cx = state.fC & 0x03; int cy = state.fC >> 2; SkDQuad quad = {{{(double) ax, (double) ay}, {(double) bx, (double) by}, {(double) cx, (double) cy} } }; SkReduceOrder reducer; int order = reducer.reduce(quad); if (order < 3) { return; } for (int tIndex = 0; tIndex <= 4; ++tIndex) { SkDPoint xy = quad.ptAtT(tIndex / 4.0); for (int h = -2; h <= 2; ++h) { for (int v = -2; v <= 2; ++v) { if (h == v && abs(h) != 1) { continue; } double x = xy.fX; double y = xy.fY; SkDLine line = {{{x - h, y - v}, {x, y}}}; testLineIntersect(state.fReporter, quad, line, x, y); state.fReporter->bumpTestCount(); SkDLine line2 = {{{x, y}, {x + h, y + v}}}; testLineIntersect(state.fReporter, quad, line2, x, y); state.fReporter->bumpTestCount(); SkDLine line3 = {{{x - h, y - v}, {x + h, y + v}}}; testLineIntersect(state.fReporter, quad, line3, x, y); state.fReporter->bumpTestCount(); } } } }
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)); } } }
void DumpT(const SkDQuad& quad, double t) { SkDLine line = {{quad.ptAtT(t), quad[0]}}; line.dump(); }
// each time through the loop, this computes values it had from the last loop // if i == j == 1, the center values are still good // otherwise, for i != 1 or j != 1, four of the values are still good // and if i == 1 ^ j == 1, an additional value is good static bool binary_search(const SkDQuad& quad1, const SkDQuad& quad2, double* t1Seed, double* t2Seed, SkDPoint* pt) { double tStep = ROUGH_EPSILON; SkDPoint t1[3], t2[3]; int calcMask = ~0; do { if (calcMask & (1 << 1)) t1[1] = quad1.ptAtT(*t1Seed); if (calcMask & (1 << 4)) t2[1] = quad2.ptAtT(*t2Seed); if (t1[1].approximatelyEqual(t2[1])) { *pt = t1[1]; #if ONE_OFF_DEBUG SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) == (%1.9g,%1.9g)\n", __FUNCTION__, t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY); #endif return true; } if (calcMask & (1 << 0)) t1[0] = quad1.ptAtT(*t1Seed - tStep); if (calcMask & (1 << 2)) t1[2] = quad1.ptAtT(*t1Seed + tStep); if (calcMask & (1 << 3)) t2[0] = quad2.ptAtT(*t2Seed - tStep); if (calcMask & (1 << 5)) t2[2] = quad2.ptAtT(*t2Seed + tStep); double dist[3][3]; // OPTIMIZE: using calcMask value permits skipping some distance calcuations // if prior loop's results are moved to correct slot for reuse dist[1][1] = t1[1].distanceSquared(t2[1]); int best_i = 1, best_j = 1; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { if (i == 1 && j == 1) { continue; } dist[i][j] = t1[i].distanceSquared(t2[j]); if (dist[best_i][best_j] > dist[i][j]) { best_i = i; best_j = j; } } } if (best_i == 1 && best_j == 1) { tStep /= 2; if (tStep < FLT_EPSILON_HALF) { break; } calcMask = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5); continue; } if (best_i == 0) { *t1Seed -= tStep; t1[2] = t1[1]; t1[1] = t1[0]; calcMask = 1 << 0; } else if (best_i == 2) { *t1Seed += tStep; t1[0] = t1[1]; t1[1] = t1[2]; calcMask = 1 << 2; } else { calcMask = 0; } if (best_j == 0) { *t2Seed -= tStep; t2[2] = t2[1]; t2[1] = t2[0]; calcMask |= 1 << 3; } else if (best_j == 2) { *t2Seed += tStep; t2[0] = t2[1]; t2[1] = t2[2]; calcMask |= 1 << 5; } } while (true); #if ONE_OFF_DEBUG SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) != (%1.9g,%1.9g) %s\n", __FUNCTION__, t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY); #endif return false; }
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; }
static bool bruteMinT(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2, TRange* lowerRange, TRange* upperRange) { double maxRadius = SkTMin(maxDist(quad1), maxDist(quad2)); double maxQuads = SkTMax(maxQuad(quad1), maxQuad(quad2)); double r = maxRadius / 2; double rStep = r / 2; SkDPoint best1 = {SK_ScalarInfinity, SK_ScalarInfinity}; SkDPoint best2 = {SK_ScalarInfinity, SK_ScalarInfinity}; int bestCCW = -1; double bestR = maxRadius; upperRange->tMin = 0; lowerRange->tMin = 1; do { do { // find upper bounds of single result TRange tRange; bool stepUp = orderTRange(reporter, quad1, quad2, r, &tRange); if (stepUp) { SkDPoint pt1 = quad1.ptAtT(tRange.t1); if (equalPoints(pt1, best1, maxQuads)) { break; } best1 = pt1; SkDPoint pt2 = quad2.ptAtT(tRange.t2); if (equalPoints(pt2, best2, maxQuads)) { break; } best2 = pt2; if (gPathOpsAngleIdeasVerbose) { SkDebugf("u bestCCW=%d ccw=%d bestMin=%1.9g:%1.9g r=%1.9g tMin=%1.9g\n", bestCCW, tRange.ccw, lowerRange->tMin, upperRange->tMin, r, tRange.tMin); } if (bestCCW >= 0 && bestCCW != (int) tRange.ccw) { if (tRange.tMin < upperRange->tMin) { upperRange->tMin = 0; } else { stepUp = false; } } if (upperRange->tMin < tRange.tMin) { bestCCW = tRange.ccw; bestR = r; *upperRange = tRange; } if (lowerRange->tMin > tRange.tMin) { *lowerRange = tRange; } } r += stepUp ? rStep : -rStep; rStep /= 2; } while (rStep > FLT_EPSILON); if (bestCCW < 0) { REPORTER_ASSERT(reporter, bestR < maxRadius); return false; } double lastHighR = bestR; r = bestR / 2; rStep = r / 2; do { // find lower bounds of single result TRange tRange; bool success = orderTRange(reporter, quad1, quad2, r, &tRange); if (success) { if (gPathOpsAngleIdeasVerbose) { SkDebugf("l bestCCW=%d ccw=%d bestMin=%1.9g:%1.9g r=%1.9g tMin=%1.9g\n", bestCCW, tRange.ccw, lowerRange->tMin, upperRange->tMin, r, tRange.tMin); } if (bestCCW != (int) tRange.ccw || upperRange->tMin < tRange.tMin) { bestCCW = tRange.ccw; *upperRange = tRange; bestR = lastHighR; break; // need to establish a new upper bounds } SkDPoint pt1 = quad1.ptAtT(tRange.t1); SkDPoint pt2 = quad2.ptAtT(tRange.t2); if (equalPoints(pt1, best1, maxQuads)) { goto breakOut; } best1 = pt1; if (equalPoints(pt2, best2, maxQuads)) { goto breakOut; } best2 = pt2; if (equalPoints(pt1, pt2, maxQuads)) { success = false; } else { if (upperRange->tMin < tRange.tMin) { *upperRange = tRange; } if (lowerRange->tMin > tRange.tMin) { *lowerRange = tRange; } } lastHighR = SkTMin(r, lastHighR); } r += success ? -rStep : rStep; rStep /= 2; } while (rStep > FLT_EPSILON); } while (rStep > FLT_EPSILON); breakOut: if (gPathOpsAngleIdeasVerbose) { SkDebugf("l a2-a1==%1.9g\n", lowerRange->a2 - lowerRange->a1); } return true; }
static double quadAngle(skiatest::Reporter* reporter, const SkDQuad& quad, double t) { const SkDVector& pt = quad.ptAtT(t) - quad[0]; double angle = (atan2(pt.fY, pt.fX) + SK_ScalarPI) * 8 / (SK_ScalarPI * 2); REPORTER_ASSERT(reporter, angle >= 0 && angle <= 8); return angle; }
static void setup(const SortSet* set, const size_t idx, SkOpSegment* seg, int* ts, const SkPoint& startPt) { SkPoint start, end; const SkPoint* data = set[idx].ptData; bool useIntersectPt = startPt.fX != 0 || startPt.fY != 0; if (useIntersectPt) { start = startPt; end = set[idx].endPt; } switch(set[idx].ptCount) { case 2: { SkASSERT(ValidPoints(data, 2)); seg->addLine(data, false, false); SkDLine dLine; dLine.set(set[idx].ptData); SkASSERT(ValidLine(dLine)); if (useIntersectPt) { break; } start = dLine.ptAtT(set[idx].tStart).asSkPoint(); end = dLine.ptAtT(set[idx].tEnd).asSkPoint(); } break; case 3: { SkASSERT(ValidPoints(data, 3)); seg->addQuad(data, false, false); SkDQuad dQuad; dQuad.set(set[idx].ptData); SkASSERT(ValidQuad(dQuad)); if (useIntersectPt) { break; } start = dQuad.ptAtT(set[idx].tStart).asSkPoint(); end = dQuad.ptAtT(set[idx].tEnd).asSkPoint(); } break; case 4: { SkASSERT(ValidPoints(data, 4)); seg->addCubic(data, false, false); SkDCubic dCubic; dCubic.set(set[idx].ptData); SkASSERT(ValidCubic(dCubic)); if (useIntersectPt) { break; } start = dCubic.ptAtT(set[idx].tStart).asSkPoint(); end = dCubic.ptAtT(set[idx].tEnd).asSkPoint(); } break; } double tStart = set[idx].tStart; double tEnd = set[idx].tEnd; seg->addT(NULL, start, tStart); seg->addT(NULL, end, tEnd); if (tStart != 0 && tEnd != 0) { seg->addT(NULL, set[idx].ptData[0], 0); } if (tStart != 1 && tEnd != 1) { seg->addT(NULL, set[idx].ptData[set[idx].ptCount - 1], 1); } int tIndex = 0; ts[0] = 0; ts[1] = 1; do { if (seg->t(tIndex) == set[idx].tStart) { ts[0] = tIndex; } if (seg->t(tIndex) == set[idx].tEnd) { ts[1] = tIndex; } if (seg->t(tIndex) >= 1) { break; } } while (++tIndex); }