bool SkDCubic::controlsContainedByEnds() const { SkDVector startTan = fPts[1] - fPts[0]; if (startTan.fX == 0 && startTan.fY == 0) { startTan = fPts[2] - fPts[0]; } SkDVector endTan = fPts[2] - fPts[3]; if (endTan.fX == 0 && endTan.fY == 0) { endTan = fPts[1] - fPts[3]; } if (startTan.dot(endTan) >= 0) { return false; } SkDLine startEdge = {{fPts[0], fPts[0]}}; startEdge[1].fX -= startTan.fY; startEdge[1].fY += startTan.fX; SkDLine endEdge = {{fPts[3], fPts[3]}}; endEdge[1].fX -= endTan.fY; endEdge[1].fY += endTan.fX; double leftStart1 = startEdge.isLeft(fPts[1]); if (leftStart1 * startEdge.isLeft(fPts[2]) < 0) { return false; } double leftEnd1 = endEdge.isLeft(fPts[1]); if (leftEnd1 * endEdge.isLeft(fPts[2]) < 0) { return false; } return leftStart1 * leftEnd1 >= 0; }
// 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; }
// OPTIMIZATION: longest can all be either lazily computed here or precomputed in setup double SkOpAngle::distEndRatio(double dist) const { double longest = 0; const SkOpSegment& segment = *this->segment(); int ptCount = SkPathOpsVerbToPoints(segment.verb()); const SkPoint* pts = segment.pts(); for (int idx1 = 0; idx1 <= ptCount - 1; ++idx1) { for (int idx2 = idx1 + 1; idx2 <= ptCount; ++idx2) { if (idx1 == idx2) { continue; } SkDVector v; v.set(pts[idx2] - pts[idx1]); double lenSq = v.lengthSquared(); longest = SkTMax(longest, lenSq); } } return sqrt(longest) / dist; }
int SkIntersections::mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const { int result = -1; for (int index = 0; index < fUsed; ++index) { if (!between(rangeStart, fT[0][index], rangeEnd)) { continue; } if (result < 0) { result = index; continue; } SkDVector best = fPt[result] - origin; SkDVector test = fPt[index] - origin; if (test.crossCheck(best) < 0) { result = index; } } return result; }
void SkOpAngle::setCurveHullSweep() { fUnorderedSweep = false; fSweep[0] = fCurvePart[1] - fCurvePart[0]; if (SkPath::kLine_Verb == fSegment->verb()) { fSweep[1] = fSweep[0]; return; } fSweep[1] = fCurvePart[2] - fCurvePart[0]; if (SkPath::kCubic_Verb != fSegment->verb()) { if (!fSweep[0].fX && !fSweep[0].fY) { fSweep[0] = fSweep[1]; } return; } SkDVector thirdSweep = fCurvePart[3] - fCurvePart[0]; if (fSweep[0].fX == 0 && fSweep[0].fY == 0) { fSweep[0] = fSweep[1]; fSweep[1] = thirdSweep; if (fSweep[0].fX == 0 && fSweep[0].fY == 0) { fSweep[0] = fSweep[1]; fCurvePart[1] = fCurvePart[3]; fIsCurve = false; } return; } double s1x3 = fSweep[0].crossCheck(thirdSweep); double s3x2 = thirdSweep.crossCheck(fSweep[1]); if (s1x3 * s3x2 >= 0) { // if third vector is on or between first two vectors return; } double s2x1 = fSweep[1].crossCheck(fSweep[0]); // FIXME: If the sweep of the cubic is greater than 180 degrees, we're in trouble // probably such wide sweeps should be artificially subdivided earlier so that never happens SkASSERT(s1x3 * s2x1 < 0 || s1x3 * s3x2 < 0); if (s3x2 * s2x1 < 0) { SkASSERT(s2x1 * s1x3 > 0); fSweep[0] = fSweep[1]; fUnorderedSweep = true; } fSweep[1] = thirdSweep; }
// determine that slop required after quad/quad finds a candidate intersection // use the cross of the tangents plus the distance from 1 or 0 as knobs DEF_TEST(PathOpsCubicQuadSlop, reporter) { // create a random non-selfintersecting cubic // break it into quadratics // offset the quadratic, measuring the slop required to find the intersection if (!gPathOpCubicQuadSlopVerbose) { // takes a while to run -- so exclude it by default return; } int results[101]; sk_bzero(results, sizeof(results)); double minCross[101]; sk_bzero(minCross, sizeof(minCross)); double maxCross[101]; sk_bzero(maxCross, sizeof(maxCross)); double sumCross[101]; sk_bzero(sumCross, sizeof(sumCross)); int foundOne = 0; int slopCount = 1; SkRandom ran; for (int index = 0; index < 10000000; ++index) { if (index % 1000 == 999) SkDebugf("."); SkDCubic cubic = {{ {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)} }}; SkIntersections i; if (i.intersect(cubic)) { continue; } SkSTArray<kCubicToQuadSubdivisionDepth, double, true> ts; cubic.toQuadraticTs(cubic.calcPrecision(), &ts); double tStart = 0; int tsCount = ts.count(); for (int i1 = 0; i1 <= tsCount; ++i1) { const double tEnd = i1 < tsCount ? ts[i1] : 1; SkDCubic part = cubic.subDivide(tStart, tEnd); SkDQuad quad = part.toQuad(); SkReduceOrder reducer; int order = reducer.reduce(quad); if (order != 3) { continue; } for (int i2 = 0; i2 < 100; ++i2) { SkDPoint endDisplacement = {ran.nextRangeF(-100, 100), ran.nextRangeF(-100, 100)}; SkDQuad nearby = {{ {quad[0].fX + endDisplacement.fX, quad[0].fY + endDisplacement.fY}, {quad[1].fX + ran.nextRangeF(-100, 100), quad[1].fY + ran.nextRangeF(-100, 100)}, {quad[2].fX - endDisplacement.fX, quad[2].fY - endDisplacement.fY} }}; order = reducer.reduce(nearby); if (order != 3) { continue; } SkIntersections locals; locals.allowNear(false); locals.intersect(quad, nearby); if (locals.used() != 1) { continue; } // brute force find actual intersection SkDLine cubicLine = {{ {0, 0}, {cubic[0].fX, cubic[0].fY } }}; SkIntersections liner; int i3; int found = -1; int foundErr = true; for (i3 = 1; i3 <= 1000; ++i3) { cubicLine[0] = cubicLine[1]; cubicLine[1] = cubic.ptAtT(i3 / 1000.); liner.reset(); liner.allowNear(false); liner.intersect(nearby, cubicLine); if (liner.used() == 0) { continue; } if (liner.used() > 1) { foundErr = true; break; } if (found > 0) { foundErr = true; break; } foundErr = false; found = i3; } if (foundErr) { continue; } SkDVector dist = liner.pt(0) - locals.pt(0); SkDVector qV = nearby.dxdyAtT(locals[0][0]); double cubicT = (found - 1 + liner[1][0]) / 1000.; SkDVector cV = cubic.dxdyAtT(cubicT); double qxc = qV.crossCheck(cV); double qvLen = qV.length(); double cvLen = cV.length(); double maxLen = SkTMax(qvLen, cvLen); qxc /= maxLen; double quadT = tStart + (tEnd - tStart) * locals[0][0]; double diffT = fabs(cubicT - quadT); int diffIdx = (int) (diffT * 100); results[diffIdx]++; double absQxc = fabs(qxc); if (sumCross[diffIdx] == 0) { minCross[diffIdx] = maxCross[diffIdx] = sumCross[diffIdx] = absQxc; } else { minCross[diffIdx] = SkTMin(minCross[diffIdx], absQxc); maxCross[diffIdx] = SkTMax(maxCross[diffIdx], absQxc); sumCross[diffIdx] += absQxc; } if (diffIdx >= 20) { #if 01 SkDebugf("cubic={{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" " quad={{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" " {{{%1.9g,%1.9g}, {%1.9g,%1.9g}}}" " qT=%1.9g cT=%1.9g dist=%1.9g cross=%1.9g\n", cubic[0].fX, cubic[0].fY, cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY, cubic[3].fX, cubic[3].fY, nearby[0].fX, nearby[0].fY, nearby[1].fX, nearby[1].fY, nearby[2].fX, nearby[2].fY, liner.pt(0).fX, liner.pt(0).fY, locals.pt(0).fX, locals.pt(0).fY, quadT, cubicT, dist.length(), qxc); #else SkDebugf("qT=%1.9g cT=%1.9g dist=%1.9g cross=%1.9g\n", quadT, cubicT, dist.length(), qxc); SkDebugf("<div id=\"slop%d\">\n", ++slopCount); SkDebugf("{{{%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}}}\n" "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}}}\n", cubic[0].fX, cubic[0].fY, cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY, cubic[3].fX, cubic[3].fY, nearby[0].fX, nearby[0].fY, nearby[1].fX, nearby[1].fY, nearby[2].fX, nearby[2].fY, liner.pt(0).fX, liner.pt(0).fY, locals.pt(0).fX, locals.pt(0).fY); SkDebugf("</div>\n\n"); #endif } ++foundOne; } tStart = tEnd; } if (++foundOne >= 100000) { break; } } #if 01 SkDebugf("slopCount=%d\n", slopCount); int max = 100; while (results[max] == 0) { --max; } for (int i = 0; i <= max; ++i) { if (i > 0 && i % 10 == 0) { SkDebugf("\n"); } SkDebugf("%d ", results[i]); } SkDebugf("min\n"); for (int i = 0; i <= max; ++i) { if (i > 0 && i % 10 == 0) { SkDebugf("\n"); } SkDebugf("%1.9g ", minCross[i]); } SkDebugf("max\n"); for (int i = 0; i <= max; ++i) { if (i > 0 && i % 10 == 0) { SkDebugf("\n"); } SkDebugf("%1.9g ", maxCross[i]); } SkDebugf("avg\n"); for (int i = 0; i <= max; ++i) { if (i > 0 && i % 10 == 0) { SkDebugf("\n"); } SkDebugf("%1.9g ", sumCross[i] / results[i]); } #else for (int i = 1; i < slopCount; ++i) { SkDebugf(" slop%d,\n", i); } #endif SkDebugf("\n"); }
/*( for quads and cubics, set up a parameterized line (e.g. LineParameters ) for points [0] to [1]. See if point [2] is on that line, or on one side or the other. If it both quads' end points are on the same side, choose the shorter tangent. If the tangents are equal, choose the better second tangent angle maybe I could set up LineParameters lazily */ bool SkOpAngle::operator<(const SkOpAngle& rh) const { double y = dy(); double ry = rh.dy(); if ((y < 0) ^ (ry < 0)) { // OPTIMIZATION: better to use y * ry < 0 ? return y < 0; } double x = dx(); double rx = rh.dx(); if (y == 0 && ry == 0 && x * rx < 0) { return x < rx; } double x_ry = x * ry; double rx_y = rx * y; double cmp = x_ry - rx_y; if (!approximately_zero(cmp)) { return cmp < 0; } if (approximately_zero(x_ry) && approximately_zero(rx_y) && !approximately_zero_squared(cmp)) { return cmp < 0; } // at this point, the initial tangent line is coincident // see if edges curl away from each other if (fSide * rh.fSide <= 0 && (!approximately_zero(fSide) || !approximately_zero(rh.fSide))) { // FIXME: running demo will trigger this assertion // (don't know if commenting out will trigger further assertion or not) // commenting it out allows demo to run in release, though return fSide < rh.fSide; } // see if either curve can be lengthened and try the tangent compare again if (/* cmp && */ (*fSpans)[fEnd].fOther != rh.fSegment // tangents not absolutely identical && (*rh.fSpans)[rh.fEnd].fOther != fSegment) { // and not intersecting SkOpAngle longer = *this; SkOpAngle rhLonger = rh; if (longer.lengthen() | rhLonger.lengthen()) { return longer < rhLonger; } } if ((fVerb == SkPath::kLine_Verb && approximately_zero(x) && approximately_zero(y)) || (rh.fVerb == SkPath::kLine_Verb && approximately_zero(rx) && approximately_zero(ry))) { // See general unsortable comment below. This case can happen when // one line has a non-zero change in t but no change in x and y. fUnsortable = true; rh.fUnsortable = true; return this < &rh; // even with no solution, return a stable sort } if ((*rh.fSpans)[SkMin32(rh.fStart, rh.fEnd)].fTiny || (*fSpans)[SkMin32(fStart, fEnd)].fTiny) { fUnsortable = true; rh.fUnsortable = true; return this < &rh; // even with no solution, return a stable sort } SkASSERT(fVerb >= SkPath::kQuad_Verb); SkASSERT(rh.fVerb >= SkPath::kQuad_Verb); // FIXME: until I can think of something better, project a ray from the // end of the shorter tangent to midway between the end points // through both curves and use the resulting angle to sort // FIXME: some of this setup can be moved to set() if it works, or cached if it's expensive double len = fTangent1.normalSquared(); double rlen = rh.fTangent1.normalSquared(); SkDLine ray; SkIntersections i, ri; int roots, rroots; bool flip = false; do { bool useThis = (len < rlen) ^ flip; const SkDCubic& part = useThis ? fCurvePart : rh.fCurvePart; SkPath::Verb partVerb = useThis ? fVerb : rh.fVerb; ray[0] = partVerb == SkPath::kCubic_Verb && part[0].approximatelyEqual(part[1]) ? part[2] : part[1]; ray[1].fX = (part[0].fX + part[partVerb].fX) / 2; ray[1].fY = (part[0].fY + part[partVerb].fY) / 2; SkASSERT(ray[0] != ray[1]); roots = (i.*CurveRay[fVerb])(fPts, ray); rroots = (ri.*CurveRay[rh.fVerb])(rh.fPts, ray); } while ((roots == 0 || rroots == 0) && (flip ^= true)); if (roots == 0 || rroots == 0) { // FIXME: we don't have a solution in this case. The interim solution // is to mark the edges as unsortable, exclude them from this and // future computations, and allow the returned path to be fragmented fUnsortable = true; rh.fUnsortable = true; return this < &rh; // even with no solution, return a stable sort } SkDPoint loc; double best = SK_ScalarInfinity; SkDVector dxy; double dist; int index; for (index = 0; index < roots; ++index) { loc = (*CurveDPointAtT[fVerb])(fPts, i[0][index]); dxy = loc - ray[0]; dist = dxy.lengthSquared(); if (best > dist) { best = dist; } } for (index = 0; index < rroots; ++index) { loc = (*CurveDPointAtT[rh.fVerb])(rh.fPts, ri[0][index]); dxy = loc - ray[0]; dist = dxy.lengthSquared(); if (best > dist) { return fSide < 0; } } return fSide > 0; }
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 double flat_measure(const SkDQuad& q) { SkDVector mid = q[1] - q[0]; SkDVector dxy = q[2] - q[0]; double length = dxy.length(); // OPTIMIZE: get rid of sqrt return fabs(mid.cross(dxy) / length); }
static void testQuadAngles(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2, int testNo, SkChunkAlloc* allocator) { SkPoint shortQuads[2][3]; SkOpContour contour; SkOpGlobalState state(NULL PATH_OPS_DEBUG_PARAMS(&contour)); contour.init(&state, false, false); makeSegment(&contour, quad1, shortQuads[0], allocator); makeSegment(&contour, quad1, shortQuads[1], allocator); SkOpSegment* seg1 = contour.first(); seg1->debugAddAngle(0, 1, allocator); SkOpSegment* seg2 = seg1->next(); seg2->debugAddAngle(0, 1, allocator); int realOverlap = PathOpsAngleTester::ConvexHullOverlaps(*seg1->debugLastAngle(), *seg2->debugLastAngle()); const SkDPoint& origin = quad1[0]; REPORTER_ASSERT(reporter, origin == quad2[0]); double a1s = atan2(origin.fY - quad1[1].fY, quad1[1].fX - origin.fX); double a1e = atan2(origin.fY - quad1[2].fY, quad1[2].fX - origin.fX); double a2s = atan2(origin.fY - quad2[1].fY, quad2[1].fX - origin.fX); double a2e = atan2(origin.fY - quad2[2].fY, quad2[2].fX - origin.fX); bool oldSchoolOverlap = radianBetween(a1s, a2s, a1e) || radianBetween(a1s, a2e, a1e) || radianBetween(a2s, a1s, a2e) || radianBetween(a2s, a1e, a2e); int overlap = quadHullsOverlap(reporter, quad1, quad2); bool realMatchesOverlap = realOverlap == overlap || SK_ScalarPI - fabs(a2s - a1s) < 0.002; if (realOverlap != overlap) { SkDebugf("\nSK_ScalarPI - fabs(a2s - a1s) = %1.9g\n", SK_ScalarPI - fabs(a2s - a1s)); } if (!realMatchesOverlap) { DumpQ(quad1, quad2, testNo); } REPORTER_ASSERT(reporter, realMatchesOverlap); if (oldSchoolOverlap != (overlap < 0)) { overlap = quadHullsOverlap(reporter, quad1, quad2); // set a breakpoint and debug if assert fires REPORTER_ASSERT(reporter, oldSchoolOverlap == (overlap < 0)); } SkDVector v1s = quad1[1] - quad1[0]; SkDVector v1e = quad1[2] - quad1[0]; SkDVector v2s = quad2[1] - quad2[0]; SkDVector v2e = quad2[2] - quad2[0]; double vDir[2] = { v1s.cross(v1e), v2s.cross(v2e) }; bool ray1In2 = v1s.cross(v2s) * vDir[1] <= 0 && v1s.cross(v2e) * vDir[1] >= 0; bool ray2In1 = v2s.cross(v1s) * vDir[0] <= 0 && v2s.cross(v1e) * vDir[0] >= 0; if (overlap >= 0) { // verify that hulls really don't overlap REPORTER_ASSERT(reporter, !ray1In2); REPORTER_ASSERT(reporter, !ray2In1); bool ctrl1In2 = v1e.cross(v2s) * vDir[1] <= 0 && v1e.cross(v2e) * vDir[1] >= 0; REPORTER_ASSERT(reporter, !ctrl1In2); bool ctrl2In1 = v2e.cross(v1s) * vDir[0] <= 0 && v2e.cross(v1e) * vDir[0] >= 0; REPORTER_ASSERT(reporter, !ctrl2In1); // check answer against reference bruteForce(reporter, quad1, quad2, overlap > 0); } // continue end point rays and see if they intersect the opposite curve SkDLine rays[] = {{{origin, quad2[2]}}, {{origin, quad1[2]}}}; const SkDQuad* quads[] = {&quad1, &quad2}; SkDVector midSpokes[2]; SkIntersections intersect[2]; double minX, minY, maxX, maxY; minX = minY = SK_ScalarInfinity; maxX = maxY = -SK_ScalarInfinity; double maxWidth = 0; bool useIntersect = false; double smallestTs[] = {1, 1}; for (unsigned index = 0; index < SK_ARRAY_COUNT(quads); ++index) { const SkDQuad& q = *quads[index]; midSpokes[index] = q.ptAtT(0.5) - origin; minX = SkTMin(SkTMin(SkTMin(minX, origin.fX), q[1].fX), q[2].fX); minY = SkTMin(SkTMin(SkTMin(minY, origin.fY), q[1].fY), q[2].fY); maxX = SkTMax(SkTMax(SkTMax(maxX, origin.fX), q[1].fX), q[2].fX); maxY = SkTMax(SkTMax(SkTMax(maxY, origin.fY), q[1].fY), q[2].fY); maxWidth = SkTMax(maxWidth, SkTMax(maxX - minX, maxY - minY)); intersect[index].intersectRay(q, rays[index]); const SkIntersections& i = intersect[index]; REPORTER_ASSERT(reporter, i.used() >= 1); bool foundZero = false; double smallT = 1; for (int idx2 = 0; idx2 < i.used(); ++idx2) { double t = i[0][idx2]; if (t == 0) { foundZero = true; continue; } if (smallT > t) { smallT = t; } } REPORTER_ASSERT(reporter, foundZero == true); if (smallT == 1) { continue; } SkDVector ray = q.ptAtT(smallT) - origin; SkDVector end = rays[index][1] - origin; if (ray.fX * end.fX < 0 || ray.fY * end.fY < 0) { continue; } double rayDist = ray.length(); double endDist = end.length(); double delta = fabs(rayDist - endDist) / maxWidth; if (delta > 1e-4) { useIntersect ^= true; } smallestTs[index] = smallT; } bool firstInside; if (useIntersect) { int sIndex = (int) (smallestTs[1] < 1); REPORTER_ASSERT(reporter, smallestTs[sIndex ^ 1] == 1); double t = smallestTs[sIndex]; const SkDQuad& q = *quads[sIndex]; SkDVector ray = q.ptAtT(t) - origin; SkDVector end = rays[sIndex][1] - origin; double rayDist = ray.length(); double endDist = end.length(); SkDVector mid = q.ptAtT(t / 2) - origin; double midXray = mid.crossCheck(ray); if (gPathOpsAngleIdeasVerbose) { SkDebugf("rayDist>endDist:%d sIndex==0:%d vDir[sIndex]<0:%d midXray<0:%d\n", rayDist > endDist, sIndex == 0, vDir[sIndex] < 0, midXray < 0); } SkASSERT(SkScalarSignAsInt(SkDoubleToScalar(midXray)) == SkScalarSignAsInt(SkDoubleToScalar(vDir[sIndex]))); firstInside = (rayDist > endDist) ^ (sIndex == 0) ^ (vDir[sIndex] < 0); } else if (overlap >= 0) { return; // answer has already been determined } else { firstInside = checkParallel(reporter, quad1, quad2); } if (overlap < 0) { SkDEBUGCODE(int realEnds =) PathOpsAngleTester::EndsIntersect(*seg1->debugLastAngle(), *seg2->debugLastAngle()); SkASSERT(realEnds == (firstInside ? 1 : 0)); }
int SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) { SkDCubic cubic; cubic.set(pointsPtr); if (cubic.monotonicInX() && cubic.monotonicInY()) { return 0; } SkScalar d[3]; SkCubicType cubicType = SkClassifyCubic(pointsPtr, d); switch (cubicType) { case kLoop_SkCubicType: { // crib code from gpu path utils that finds t values where loop self-intersects // use it to find mid of t values which should be a friendly place to chop SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]); SkScalar ls = d[1] - tempSqrt; SkScalar lt = 2.f * d[0]; SkScalar ms = d[1] + tempSqrt; SkScalar mt = 2.f * d[0]; if (roughly_between(0, ls, lt) && roughly_between(0, ms, mt)) { ls = ls / lt; ms = ms / mt; SkASSERT(roughly_between(0, ls, 1) && roughly_between(0, ms, 1)); t[0] = (ls + ms) / 2; SkASSERT(roughly_between(0, *t, 1)); return (int) (t[0] > 0 && t[0] < 1); } } // fall through if no t value found case kSerpentine_SkCubicType: case kCusp_SkCubicType: { double inflectionTs[2]; int infTCount = cubic.findInflections(inflectionTs); double maxCurvature[3]; int roots = cubic.findMaxCurvature(maxCurvature); #if DEBUG_CUBIC_SPLIT SkDebugf("%s\n", __FUNCTION__); cubic.dump(); for (int index = 0; index < infTCount; ++index) { SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]); SkDPoint pt = cubic.ptAtT(inflectionTs[index]); SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]); SkDLine perp = {{pt - dPt, pt + dPt}}; perp.dump(); } for (int index = 0; index < roots; ++index) { SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]); SkDPoint pt = cubic.ptAtT(maxCurvature[index]); SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]); SkDLine perp = {{pt - dPt, pt + dPt}}; perp.dump(); } #endif if (infTCount == 2) { for (int index = 0; index < roots; ++index) { if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) { t[0] = maxCurvature[index]; return (int) (t[0] > 0 && t[0] < 1); } } } else { int resultCount = 0; // FIXME: constant found through experimentation -- maybe there's a better way.... double precision = cubic.calcPrecision() * 2; for (int index = 0; index < roots; ++index) { double testT = maxCurvature[index]; if (0 >= testT || testT >= 1) { continue; } // don't call dxdyAtT since we want (0,0) results SkDVector dPt = { derivative_at_t(&cubic.fPts[0].fX, testT), derivative_at_t(&cubic.fPts[0].fY, testT) }; double dPtLen = dPt.length(); if (dPtLen < precision) { t[resultCount++] = testT; } } if (!resultCount && infTCount == 1) { t[0] = inflectionTs[0]; resultCount = (int) (t[0] > 0 && t[0] < 1); } return resultCount; } } default: ; } return 0; }