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 SkDQuad& quad = quadraticLines[index]; 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 SkDQuad& quad = quadraticModEpsilonLines[index]; 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 oneOffTest(skiatest::Reporter* reporter) { for (size_t index = 0; index < testSetCount; ++index) { const SkDQuad& quad = testSet[index]; SkReduceOrder reducer; SkDEBUGCODE(int result = ) reducer.reduce(quad); SkASSERT(result == 3); } }
SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkPoint* reducePts) { SkDCubic cubic; cubic.set(a); SkReduceOrder reducer; int order = reducer.reduce(cubic, kAllow_Quadratics); if (order == 2 || order == 3) { // cubic became line or quad for (int index = 0; index < order; ++index) { *reducePts++ = reducer.fQuad[index].asSkPoint(); } } return SkPathOpsPointsToVerb(order - 1); }
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(PathOpsConicLineIntersection, reporter) { for (size_t index = 0; index < lineConicTests_count; ++index) { int iIndex = static_cast<int>(index); const SkDConic& conic = lineConicTests[index].conic; SkASSERT(ValidConic(conic)); const SkDLine& line = lineConicTests[index].line; SkASSERT(ValidLine(line)); SkReduceOrder reducer; SkPoint pts[3] = { conic.fPts.fPts[0].asSkPoint(), conic.fPts.fPts[1].asSkPoint(), conic.fPts.fPts[2].asSkPoint() }; SkPoint reduced[3]; SkPath::Verb order1 = SkReduceOrder::Conic(pts, conic.fWeight, reduced); if (order1 != SkPath::kConic_Verb) { SkDebugf("%s [%d] conic verb=%d\n", __FUNCTION__, iIndex, order1); REPORTER_ASSERT(reporter, 0); } int order2 = reducer.reduce(line); 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, conic, line, flipped); REPORTER_ASSERT(reporter, result == lineConicTests[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 = conic.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(lineConicTests[index].expected[0]) && (lineConicTests[index].result == 1 || !t1.approximatelyEqual(lineConicTests[index].expected[1]))) { SkDebugf("%s t1=(%1.9g,%1.9g)\n", __FUNCTION__, t1.fX, t1.fY); REPORTER_ASSERT(reporter, 0); } } } }
SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkPoint* reducePts) { if (SkDPoint::ApproximatelyEqual(a[0], a[1]) && SkDPoint::ApproximatelyEqual(a[0], a[2]) && SkDPoint::ApproximatelyEqual(a[0], a[3])) { reducePts[0] = a[0]; return SkPath::kMove_Verb; } SkDCubic cubic; cubic.set(a); SkReduceOrder reducer; int order = reducer.reduce(cubic, kAllow_Quadratics); if (order == 2 || order == 3) { // cubic became line or quad for (int index = 0; index < order; ++index) { *reducePts++ = reducer.fQuad[index].asSkPoint(); } } return SkPathOpsPointsToVerb(order - 1); }
// 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(); } } } }
// 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"); }
// flavor that returns T values only, deferring computing the quads until they are needed // FIXME: when called from recursive intersect 2, this could take the original cubic // and do a more precise job when calling chop at and sub divide by computing the fractional ts. // it would still take the prechopped cubic for reduce order and find cubic inflections void SkDCubic::toQuadraticTs(double precision, SkTDArray<double>* ts) const { SkReduceOrder reducer; int order = reducer.reduce(*this, SkReduceOrder::kAllow_Quadratics, SkReduceOrder::kFill_Style); if (order < 3) { return; } double inflectT[5]; int inflections = findInflections(inflectT); SkASSERT(inflections <= 2); if (!endsAreExtremaInXOrY()) { inflections += findMaxCurvature(&inflectT[inflections]); SkASSERT(inflections <= 5); } QSort<double>(inflectT, &inflectT[inflections - 1]); // OPTIMIZATION: is this filtering common enough that it needs to be pulled out into its // own subroutine? while (inflections && approximately_less_than_zero(inflectT[0])) { memmove(inflectT, &inflectT[1], sizeof(inflectT[0]) * --inflections); } int start = 0; do { int next = start + 1; if (next >= inflections) { break; } if (!approximately_equal(inflectT[start], inflectT[next])) { ++start; continue; } memmove(&inflectT[start], &inflectT[next], sizeof(inflectT[0]) * (--inflections - start)); } while (true); while (inflections && approximately_greater_than_one(inflectT[inflections - 1])) { --inflections; } SkDCubicPair pair; if (inflections == 1) { pair = chopAt(inflectT[0]); int orderP1 = reducer.reduce(pair.first(), SkReduceOrder::kNo_Quadratics, SkReduceOrder::kFill_Style); if (orderP1 < 2) { --inflections; } else { int orderP2 = reducer.reduce(pair.second(), SkReduceOrder::kNo_Quadratics, SkReduceOrder::kFill_Style); if (orderP2 < 2) { --inflections; } } } if (inflections == 0 && add_simple_ts(*this, precision, ts)) { return; } if (inflections == 1) { pair = chopAt(inflectT[0]); addTs(pair.first(), precision, 0, inflectT[0], ts); addTs(pair.second(), precision, inflectT[0], 1, ts); return; } if (inflections > 1) { SkDCubic part = subDivide(0, inflectT[0]); addTs(part, precision, 0, inflectT[0], ts); int last = inflections - 1; for (int idx = 0; idx < last; ++idx) { part = subDivide(inflectT[idx], inflectT[idx + 1]); addTs(part, precision, inflectT[idx], inflectT[idx + 1], ts); } part = subDivide(inflectT[last], 1); addTs(part, precision, inflectT[last], 1, ts); return; } addTs(*this, precision, 0, 1, ts); }