// 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; }
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; } } }
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; }
static void oneOffTest(skiatest::Reporter* reporter) { for (size_t index = 0; index < testSetCount; ++index) { const QuadPts& q = testSet[index]; SkDQuad quad; quad.debugSet(q.fPts); SkReduceOrder reducer; SkDEBUGCODE(int result = ) reducer.reduce(quad); SkASSERT(result == 3); } }
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); } }
SkPath::Verb SkReduceOrder::Quad(const SkPoint a[3], SkPoint* reducePts) { SkDQuad quad; quad.set(a); SkReduceOrder reducer; int order = reducer.reduce(quad); if (order == 2) { // quad became line for (int index = 0; index < order; ++index) { *reducePts++ = reducer.fLine[index].asSkPoint(); } } return SkPathOpsPointsToVerb(order - 1); }
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 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); }
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"); }
static int check_linear(const SkDQuad& quad, int minX, int maxX, int minY, int maxY, SkDQuad& reduction) { if (!quad.isLinear(0, 2)) { return 0; } // four are colinear: return line formed by outside reduction[0] = quad[0]; reduction[1] = quad[2]; return reductionLineCount(reduction); }
// 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 standardTestCases(skiatest::Reporter* reporter) { size_t index; SkReduceOrder reducer; int order; enum { RunAll, RunQuadraticLines, RunQuadraticModLines, RunNone } run = RunAll; int firstTestIndex = 0; #if 0 run = RunQuadraticLines; firstTestIndex = 1; #endif int firstQuadraticLineTest = run == RunAll ? 0 : run == RunQuadraticLines ? firstTestIndex : SK_MaxS32; int firstQuadraticModLineTest = run == RunAll ? 0 : run == RunQuadraticModLines ? firstTestIndex : SK_MaxS32; for (index = firstQuadraticLineTest; index < quadraticLines_count; ++index) { const QuadPts& q = quadraticLines[index]; SkDQuad quad; quad.debugSet(q.fPts); order = reducer.reduce(quad); if (order != 2) { SkDebugf("[%d] line quad order=%d\n", (int) index, order); } } for (index = firstQuadraticModLineTest; index < quadraticModEpsilonLines_count; ++index) { const QuadPts& q = quadraticModEpsilonLines[index]; SkDQuad quad; quad.debugSet(q.fPts); order = reducer.reduce(quad); if (order != 2 && order != 3) { // FIXME: data probably is not good SkDebugf("[%d] line mod quad order=%d\n", (int) index, order); } } }
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 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])); } }
static int check_linear(const SkDQuad& quad, int minX, int maxX, int minY, int maxY, SkDQuad& reduction) { int startIndex = 0; int endIndex = 2; while (quad[startIndex].approximatelyEqual(quad[endIndex])) { --endIndex; if (endIndex == 0) { SkDebugf("%s shouldn't get here if all four points are about equal", __FUNCTION__); SkASSERT(0); } } if (!quad.isLinear(startIndex, endIndex)) { return 0; } // four are colinear: return line formed by outside reduction[0] = quad[0]; reduction[1] = quad[2]; return reductionLineCount(reduction); }
static int findRoots(const SkDQuadImplicit& i, const SkDQuad& quad, double roots[4], bool oneHint, bool flip, int firstCubicRoot) { SkDQuad flipped; const SkDQuad& q = flip ? (flipped = quad.flip()) : quad; double a, b, c; SkDQuad::SetABC(&q[0].fX, &a, &b, &c); double d, e, f; SkDQuad::SetABC(&q[0].fY, &d, &e, &f); const double t4 = i.x2() * a * a + i.xy() * a * d + i.y2() * d * d; const double t3 = 2 * i.x2() * a * b + i.xy() * (a * e + b * d) + 2 * i.y2() * d * e; const double t2 = i.x2() * (b * b + 2 * a * c) + i.xy() * (c * d + b * e + a * f) + i.y2() * (e * e + 2 * d * f) + i.x() * a + i.y() * d; const double t1 = 2 * i.x2() * b * c + i.xy() * (c * e + b * f) + 2 * i.y2() * e * f + i.x() * b + i.y() * e; const double t0 = i.x2() * c * c + i.xy() * c * f + i.y2() * f * f + i.x() * c + i.y() * f + i.c(); int rootCount = SkReducedQuarticRoots(t4, t3, t2, t1, t0, oneHint, roots); if (rootCount < 0) { rootCount = SkQuarticRootsReal(firstCubicRoot, t4, t3, t2, t1, t0, roots); } if (flip) { for (int index = 0; index < rootCount; ++index) { roots[index] = 1 - roots[index]; } } return rootCount; }
// Up promote the quad to a cubic. // OPTIMIZATION If this is a common use case, optimize by duplicating // the intersect 3 loop to avoid the promotion / demotion code int SkIntersections::intersect(const SkDCubic& cubic, const SkDQuad& quad) { SkDCubic up = quad.toCubic(); (void) intersect(cubic, up); return used(); }
// 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"); }
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; }
void DumpT(const SkDQuad& quad, double t) { SkDLine line = {{quad.ptAtT(t), quad[0]}}; line.dump(); }
static void dumpTestCase(const SkDQuad& quad1, const SkDQuad& quad2, int testNo) { SkDebugf("<div id=\"quad%d\">\n", testNo); quad1.dumpComma(","); quad2.dump(); SkDebugf("</div>\n\n"); }
// 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 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 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 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); }