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; }
// 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; }
// give up when changing t no longer moves point // also, copy point rather than recompute it when it does change double SkDCubic::binarySearch(double min, double max, double axisIntercept, SearchAxis xAxis) const { double t = (min + max) / 2; double step = (t - min) / 2; SkDPoint cubicAtT = ptAtT(t); double calcPos = (&cubicAtT.fX)[xAxis]; double calcDist = calcPos - axisIntercept; do { double priorT = t - step; SkOPASSERT(priorT >= min); SkDPoint lessPt = ptAtT(priorT); if (approximately_equal_half(lessPt.fX, cubicAtT.fX) && approximately_equal_half(lessPt.fY, cubicAtT.fY)) { return -1; // binary search found no point at this axis intercept } double lessDist = (&lessPt.fX)[xAxis] - axisIntercept; #if DEBUG_CUBIC_BINARY_SEARCH SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist, step, lessDist); #endif double lastStep = step; step /= 2; if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) { t = priorT; } else { double nextT = t + lastStep; if (nextT > max) { return -1; } SkDPoint morePt = ptAtT(nextT); if (approximately_equal_half(morePt.fX, cubicAtT.fX) && approximately_equal_half(morePt.fY, cubicAtT.fY)) { return -1; // binary search found no point at this axis intercept } double moreDist = (&morePt.fX)[xAxis] - axisIntercept; if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) { continue; } t = nextT; } SkDPoint testAtT = ptAtT(t); cubicAtT = testAtT; calcPos = (&cubicAtT.fX)[xAxis]; calcDist = calcPos - axisIntercept; } while (!approximately_equal(calcPos, axisIntercept)); return t; }
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? }
SkDPoint SkDQuad::top(double startT, double endT) const { SkDQuad sub = subDivide(startT, endT); SkDPoint topPt = sub[0]; if (topPt.fY > sub[2].fY || (topPt.fY == sub[2].fY && topPt.fX > sub[2].fX)) { topPt = sub[2]; } if (!between(sub[0].fY, sub[1].fY, sub[2].fY)) { double extremeT; if (FindExtrema(sub[0].fY, sub[1].fY, sub[2].fY, &extremeT)) { extremeT = startT + (endT - startT) * extremeT; SkDPoint test = ptAtT(extremeT); if (topPt.fY > test.fY || (topPt.fY == test.fY && topPt.fX > test.fX)) { topPt = test; } } } return topPt; }
SkDPoint SkDCubic::top(double startT, double endT) const { SkDCubic sub = subDivide(startT, endT); SkDPoint topPt = sub[0]; if (topPt.fY > sub[3].fY || (topPt.fY == sub[3].fY && topPt.fX > sub[3].fX)) { topPt = sub[3]; } double extremeTs[2]; if (!sub.monotonicInY()) { int roots = FindExtrema(sub[0].fY, sub[1].fY, sub[2].fY, sub[3].fY, extremeTs); for (int index = 0; index < roots; ++index) { double t = startT + (endT - startT) * extremeTs[index]; SkDPoint mid = ptAtT(t); if (topPt.fY > mid.fY || (topPt.fY == mid.fY && topPt.fX > mid.fX)) { topPt = mid; } } } return topPt; }