static double testArc(skiatest::Reporter* reporter, const SkDQuad& quad, const SkDQuad& arcRef, int octant) { SkDQuad arc = arcRef; SkDVector offset = {quad[0].fX, quad[0].fY}; arc[0] += offset; arc[1] += offset; arc[2] += offset; SkIntersections i; i.intersect(arc, quad); if (i.used() == 0) { return -1; } int smallest = -1; double t = 2; for (int idx = 0; idx < i.used(); ++idx) { if (i[0][idx] > 1 || i[0][idx] < 0) { i.reset(); i.intersect(arc, quad); } if (i[1][idx] > 1 || i[1][idx] < 0) { i.reset(); i.intersect(arc, quad); } if (t > i[1][idx]) { smallest = idx; t = i[1][idx]; } } REPORTER_ASSERT(reporter, smallest >= 0); REPORTER_ASSERT(reporter, t >= 0 && t <= 1); return i[1][smallest]; }
static void TestLineIntersection(skiatest::Reporter* reporter) { size_t index; for (index = 0; index < tests_count; ++index) { const SkDLine& line1 = tests[index][0]; const SkDLine& line2 = tests[index][1]; SkIntersections ts; int pts = ts.intersect(line1, line2); REPORTER_ASSERT(reporter, pts); for (int i = 0; i < pts; ++i) { SkDPoint result1 = line1.xyAtT(ts[0][i]); SkDPoint result2 = line2.xyAtT(ts[1][i]); if (!result1.approximatelyEqual(result2)) { REPORTER_ASSERT(reporter, pts != 1); result2 = line2.xyAtT(ts[1][i ^ 1]); REPORTER_ASSERT(reporter, result1.approximatelyEqual(result2)); } } } for (index = 0; index < noIntersect_count; ++index) { const SkDLine& line1 = noIntersect[index][0]; const SkDLine& line2 = noIntersect[index][1]; SkIntersections ts; int pts = ts.intersect(line1, line2); REPORTER_ASSERT(reporter, !pts); } }
static void intersectWithOrder(const SkDQuad& simple1, int order1, const SkDQuad& simple2, int order2, SkIntersections& i) { if (order1 == 3 && order2 == 3) { i.intersect(simple1, simple2); } else if (order1 <= 2 && order2 <= 2) { i.intersect((const SkDLine&) simple1, (const SkDLine&) simple2); } else if (order1 == 3 && order2 <= 2) { i.intersect(simple1, (const SkDLine&) simple2); } else { SkASSERT(order1 <= 2 && order2 == 3); i.intersect(simple2, (const SkDLine&) simple1); i.swapPts(); } }
static void cubicQuadIntersection(skiatest::Reporter* reporter, int index) { int iIndex = static_cast<int>(index); const SkDCubic& cubic = quadCubicTests[index].cubic; SkASSERT(ValidCubic(cubic)); const SkDQuad& quad = quadCubicTests[index].quad; SkASSERT(ValidQuad(quad)); SkReduceOrder reduce1; SkReduceOrder reduce2; int order1 = reduce1.reduce(cubic, SkReduceOrder::kNo_Quadratics); int order2 = reduce2.reduce(quad); if (order1 != 4) { SkDebugf("[%d] cubic order=%d\n", iIndex, order1); REPORTER_ASSERT(reporter, 0); } if (order2 != 3) { SkDebugf("[%d] quad order=%d\n", iIndex, order2); REPORTER_ASSERT(reporter, 0); } SkIntersections i; int roots = i.intersect(cubic, quad); for (int pt = 0; pt < roots; ++pt) { double tt1 = i[0][pt]; SkDPoint xy1 = cubic.ptAtT(tt1); double tt2 = i[1][pt]; SkDPoint xy2 = quad.ptAtT(tt2); if (!xy1.approximatelyEqual(xy2)) { SkDebugf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, iIndex, pt, tt1, xy1.fX, xy1.fY, tt2, xy2.fX, xy2.fY); } REPORTER_ASSERT(reporter, xy1.approximatelyEqual(xy2)); } reporter->bumpTestCount(); }
static void oneOff(skiatest::Reporter* reporter, const SkDConic& c1, const SkDConic& c2, bool coin) { #if DEBUG_VISUALIZE_CONICS writeFrames(); #endif chopBothWays(c1, 0.5, "c1"); chopBothWays(c2, 0.5, "c2"); #if DEBUG_VISUALIZE_CONICS writeDPng(c1, "d1"); writeDPng(c2, "d2"); #endif SkASSERT(ValidConic(c1)); SkASSERT(ValidConic(c2)); SkIntersections intersections; intersections.intersect(c1, c2); if (coin && intersections.used() != 2) { SkDebugf(""); } REPORTER_ASSERT(reporter, !coin || intersections.used() == 2); double tt1, tt2; SkDPoint xy1, xy2; for (int pt3 = 0; pt3 < intersections.used(); ++pt3) { tt1 = intersections[0][pt3]; xy1 = c1.ptAtT(tt1); tt2 = intersections[1][pt3]; xy2 = c2.ptAtT(tt2); const SkDPoint& iPt = intersections.pt(pt3); REPORTER_ASSERT(reporter, xy1.approximatelyEqual(iPt)); REPORTER_ASSERT(reporter, xy2.approximatelyEqual(iPt)); REPORTER_ASSERT(reporter, xy1.approximatelyEqual(xy2)); } reporter->bumpTestCount(); }
static int doIntersect(SkIntersections& intersections, const SkDConic& conic, const SkDLine& line, bool& flipped) { int result; flipped = false; if (line[0].fX == line[1].fX) { double top = line[0].fY; double bottom = line[1].fY; flipped = top > bottom; if (flipped) { SkTSwap<double>(top, bottom); } result = intersections.vertical(conic, top, bottom, line[0].fX, flipped); } else if (line[0].fY == line[1].fY) { double left = line[0].fX; double right = line[1].fX; flipped = left > right; if (flipped) { SkTSwap<double>(left, right); } result = intersections.horizontal(conic, left, right, line[0].fY, flipped); } else { intersections.intersect(conic, line); result = intersections.used(); } return result; }
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; } } }
// 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 selfOneOff(skiatest::Reporter* reporter, int index) { const SkDCubic& cubic = selfSet[index]; #if ONE_OFF_DEBUG int idx2; double max[3]; int ts = cubic.findMaxCurvature(max); for (idx2 = 0; idx2 < ts; ++idx2) { SkDebugf("%s max[%d]=%1.9g (%1.9g, %1.9g)\n", __FUNCTION__, idx2, max[idx2], cubic.ptAtT(max[idx2]).fX, cubic.ptAtT(max[idx2]).fY); } SkTArray<double, true> ts1; SkTArray<SkDQuad, true> quads1; cubic.toQuadraticTs(cubic.calcPrecision(), &ts1); for (idx2 = 0; idx2 < ts1.count(); ++idx2) { SkDebugf("%s t[%d]=%1.9g\n", __FUNCTION__, idx2, ts1[idx2]); } CubicToQuads(cubic, cubic.calcPrecision(), quads1); for (idx2 = 0; idx2 < quads1.count(); ++idx2) { const SkDQuad& q = quads1[idx2]; SkDebugf(" {{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}},\n", q[0].fX, q[0].fY, q[1].fX, q[1].fY, q[2].fX, q[2].fY); } SkDebugf("\n"); #endif SkIntersections i; int result = i.intersect(cubic); REPORTER_ASSERT(reporter, result == 1); REPORTER_ASSERT(reporter, i.used() == 1); REPORTER_ASSERT(reporter, !approximately_equal(i[0][0], i[1][0])); SkDPoint pt1 = cubic.ptAtT(i[0][0]); SkDPoint pt2 = cubic.ptAtT(i[1][0]); REPORTER_ASSERT(reporter, pt1.approximatelyEqual(pt2)); reporter->bumpTestCount(); }
static int doIntersect(SkIntersections& intersections, const SkDQuad& quad, const SkDLine& line, bool& flipped) { int result; flipped = false; if (line[0].fX == line[1].fX) { double top = line[0].fY; double bottom = line[1].fY; flipped = top > bottom; if (flipped) { using std::swap; swap(top, bottom); } result = intersections.vertical(quad, top, bottom, line[0].fX, flipped); } else if (line[0].fY == line[1].fY) { double left = line[0].fX; double right = line[1].fX; flipped = left > right; if (flipped) { using std::swap; swap(left, right); } result = intersections.horizontal(quad, left, right, line[0].fY, flipped); } else { intersections.intersect(quad, line); result = intersections.used(); } return result; }
static void PathOpsCubicLineIntersectionTest(skiatest::Reporter* reporter) { for (size_t index = 0; index < lineCubicTests_count; ++index) { int iIndex = static_cast<int>(index); const SkDCubic& cubic = lineCubicTests[index].cubic; const SkDLine& line = lineCubicTests[index].line; SkReduceOrder reduce1; SkReduceOrder reduce2; int order1 = reduce1.reduce(cubic, SkReduceOrder::kNo_Quadratics, SkReduceOrder::kFill_Style); int order2 = reduce2.reduce(line); if (order1 < 4) { SkDebugf("[%d] cubic order=%d\n", iIndex, order1); REPORTER_ASSERT(reporter, 0); } if (order2 < 2) { SkDebugf("[%d] line order=%d\n", iIndex, order2); REPORTER_ASSERT(reporter, 0); } if (order1 == 4 && order2 == 2) { SkIntersections i; int roots = i.intersect(cubic, line); for (int pt = 0; pt < roots; ++pt) { double tt1 = i[0][pt]; SkDPoint xy1 = cubic.xyAtT(tt1); double tt2 = i[1][pt]; SkDPoint xy2 = line.xyAtT(tt2); if (!xy1.approximatelyEqual(xy2)) { SkDebugf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, iIndex, pt, tt1, xy1.fX, xy1.fY, tt2, xy2.fX, xy2.fY); } REPORTER_ASSERT(reporter, xy1.approximatelyEqual(xy2)); } } } }
static void selfOneOff(skiatest::Reporter* reporter, int index) { const CubicPts& cubic = selfSet[index]; SkPoint c[4]; for (int i = 0; i < 4; ++i) { c[i] = cubic.fPts[i].asSkPoint(); } SkScalar loopT; SkScalar d[3]; SkCubicType cubicType = SkClassifyCubic(c, d); if (SkDCubic::ComplexBreak(c, &loopT) && cubicType == SkCubicType::kLoop_SkCubicType) { SkIntersections i; SkPoint twoCubics[7]; SkChopCubicAt(c, twoCubics, loopT); SkDCubic chopped[2]; chopped[0].set(&twoCubics[0]); chopped[1].set(&twoCubics[3]); int result = i.intersect(chopped[0], chopped[1]); REPORTER_ASSERT(reporter, result == 2); REPORTER_ASSERT(reporter, i.used() == 2); for (int index = 0; index < result; ++index) { SkDPoint pt1 = chopped[0].ptAtT(i[0][index]); SkDPoint pt2 = chopped[1].ptAtT(i[1][index]); REPORTER_ASSERT(reporter, pt1.approximatelyEqual(pt2)); reporter->bumpTestCount(); } } }
static void oneOff(skiatest::Reporter* reporter, const SkDCubic& cubic1, const SkDCubic& cubic2, bool coin) { SkASSERT(ValidCubic(cubic1)); SkASSERT(ValidCubic(cubic2)); #if ONE_OFF_DEBUG SkDebugf("computed quadratics given\n"); SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", cubic1[0].fX, cubic1[0].fY, cubic1[1].fX, cubic1[1].fY, cubic1[2].fX, cubic1[2].fY, cubic1[3].fX, cubic1[3].fY); SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", cubic2[0].fX, cubic2[0].fY, cubic2[1].fX, cubic2[1].fY, cubic2[2].fX, cubic2[2].fY, cubic2[3].fX, cubic2[3].fY); #endif SkTArray<SkDQuad, true> quads1; CubicToQuads(cubic1, cubic1.calcPrecision(), quads1); #if ONE_OFF_DEBUG SkDebugf("computed quadratics set 1\n"); for (int index = 0; index < quads1.count(); ++index) { const SkDQuad& q = quads1[index]; SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", q[0].fX, q[0].fY, q[1].fX, q[1].fY, q[2].fX, q[2].fY); } #endif SkTArray<SkDQuad, true> quads2; CubicToQuads(cubic2, cubic2.calcPrecision(), quads2); #if ONE_OFF_DEBUG SkDebugf("computed quadratics set 2\n"); for (int index = 0; index < quads2.count(); ++index) { const SkDQuad& q = quads2[index]; SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", q[0].fX, q[0].fY, q[1].fX, q[1].fY, q[2].fX, q[2].fY); } #endif SkIntersections intersections; intersections.intersect(cubic1, cubic2); REPORTER_ASSERT(reporter, !coin || intersections.used() == 2); double tt1, tt2; SkDPoint xy1, xy2; for (int pt3 = 0; pt3 < intersections.used(); ++pt3) { tt1 = intersections[0][pt3]; xy1 = cubic1.ptAtT(tt1); tt2 = intersections[1][pt3]; xy2 = cubic2.ptAtT(tt2); const SkDPoint& iPt = intersections.pt(pt3); #if ONE_OFF_DEBUG SkDebugf("%s t1=%1.9g (%1.9g, %1.9g) (%1.9g, %1.9g) (%1.9g, %1.9g) t2=%1.9g\n", __FUNCTION__, tt1, xy1.fX, xy1.fY, iPt.fX, iPt.fY, xy2.fX, xy2.fY, tt2); #endif REPORTER_ASSERT(reporter, xy1.approximatelyEqual(iPt)); REPORTER_ASSERT(reporter, xy2.approximatelyEqual(iPt)); REPORTER_ASSERT(reporter, xy1.approximatelyEqual(xy2)); } reporter->bumpTestCount(); }
static void standardTestCases(skiatest::Reporter* reporter) { for (size_t index = firstCubicIntersectionTest; index < tests_count; ++index) { int iIndex = static_cast<int>(index); const CubicPts& cubic1 = tests[index][0]; const CubicPts& cubic2 = tests[index][1]; SkDCubic c1, c2; c1.debugSet(cubic1.fPts); c2.debugSet(cubic2.fPts); SkReduceOrder reduce1, reduce2; int order1 = reduce1.reduce(c1, SkReduceOrder::kNo_Quadratics); int order2 = reduce2.reduce(c2, SkReduceOrder::kNo_Quadratics); const bool showSkipped = false; if (order1 < 4) { if (showSkipped) { SkDebugf("%s [%d] cubic1 order=%d\n", __FUNCTION__, iIndex, order1); } continue; } if (order2 < 4) { if (showSkipped) { SkDebugf("%s [%d] cubic2 order=%d\n", __FUNCTION__, iIndex, order2); } continue; } SkIntersections tIntersections; tIntersections.intersect(c1, c2); if (!tIntersections.used()) { if (showSkipped) { SkDebugf("%s [%d] no intersection\n", __FUNCTION__, iIndex); } continue; } if (tIntersections.isCoincident(0)) { if (showSkipped) { SkDebugf("%s [%d] coincident\n", __FUNCTION__, iIndex); } continue; } for (int pt = 0; pt < tIntersections.used(); ++pt) { double tt1 = tIntersections[0][pt]; SkDPoint xy1 = c1.ptAtT(tt1); double tt2 = tIntersections[1][pt]; SkDPoint xy2 = c2.ptAtT(tt2); if (!xy1.approximatelyEqual(xy2)) { SkDebugf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, xy1.fX, xy1.fY, tt2, xy2.fX, xy2.fY); } REPORTER_ASSERT(reporter, xy1.approximatelyEqual(xy2)); } reporter->bumpTestCount(); } }
static void hullIntersect(const SkDQuad& q1, const SkDQuad& q2) { SkDebugf("%s", __FUNCTION__); SkIntersections ts; for (int i1 = 0; i1 < 3; ++i1) { SkDLine l1 = {{q1[i1], q1[(i1 + 1) % 3]}}; for (int i2 = 0; i2 < 3; ++i2) { SkDLine l2 = {{q2[i2], q2[(i2 + 1) % 3]}}; if (ts.intersect(l1, l2)) { SkDebugf(" [%d,%d]", i1, i2); } } } SkDebugf("\n"); }
static void oneOff(skiatest::Reporter* reporter, const CubicPts& cubic1, const CubicPts& cubic2, bool coin) { SkDCubic c1, c2; c1.debugSet(cubic1.fPts); c2.debugSet(cubic2.fPts); SkASSERT(ValidCubic(c1)); SkASSERT(ValidCubic(c2)); #if ONE_OFF_DEBUG SkDebugf("computed quadratics given\n"); SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", cubic1[0].fX, cubic1[0].fY, cubic1[1].fX, cubic1[1].fY, cubic1[2].fX, cubic1[2].fY, cubic1[3].fX, cubic1[3].fY); SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", cubic2[0].fX, cubic2[0].fY, cubic2[1].fX, cubic2[1].fY, cubic2[2].fX, cubic2[2].fY, cubic2[3].fX, cubic2[3].fY); #endif SkIntersections intersections; intersections.intersect(c1, c2); #if DEBUG_T_SECT_DUMP == 3 SkDebugf("</div>\n\n"); SkDebugf("<script type=\"text/javascript\">\n\n"); SkDebugf("var testDivs = [\n"); for (int index = 1; index <= gDumpTSectNum; ++index) { SkDebugf("sect%d,\n", index); } #endif if (coin && intersections.used() < 2) { SkDebugf(""); } REPORTER_ASSERT(reporter, !coin || intersections.used() >= 2); double tt1, tt2; SkDPoint xy1, xy2; for (int pt3 = 0; pt3 < intersections.used(); ++pt3) { tt1 = intersections[0][pt3]; xy1 = c1.ptAtT(tt1); tt2 = intersections[1][pt3]; xy2 = c2.ptAtT(tt2); const SkDPoint& iPt = intersections.pt(pt3); #if ONE_OFF_DEBUG SkDebugf("%s t1=%1.9g (%1.9g, %1.9g) (%1.9g, %1.9g) (%1.9g, %1.9g) t2=%1.9g\n", __FUNCTION__, tt1, xy1.fX, xy1.fY, iPt.fX, iPt.fY, xy2.fX, xy2.fY, tt2); #endif REPORTER_ASSERT(reporter, xy1.approximatelyEqual(iPt)); REPORTER_ASSERT(reporter, xy2.approximatelyEqual(iPt)); REPORTER_ASSERT(reporter, xy1.approximatelyEqual(xy2)); } reporter->bumpTestCount(); }
static void testOneCoincident(skiatest::Reporter* reporter, const SkDLine& line1, const SkDLine& line2) { SkASSERT(ValidLine(line1)); SkASSERT(ValidLine(line2)); SkIntersections ts2; int pts2 = ts2.intersect(line1, line2); REPORTER_ASSERT(reporter, pts2 == 2); REPORTER_ASSERT(reporter, pts2 == ts2.used()); check_results(reporter, line1, line2, ts2); #if 0 SkIntersections ts; int pts = ts.intersect(line1, line2); REPORTER_ASSERT(reporter, pts == pts2); REPORTER_ASSERT(reporter, pts == 2); REPORTER_ASSERT(reporter, pts == ts.used()); check_results(reporter, line1, line2, ts); #endif }
static void PathOpsCubicQuadIntersectionTest(skiatest::Reporter* reporter) { for (size_t index = 0; index < quadCubicTests_count; ++index) { int iIndex = static_cast<int>(index); const SkDCubic& cubic = quadCubicTests[index].cubic; const SkDQuad& quad = quadCubicTests[index].quad; SkReduceOrder reduce1; SkReduceOrder reduce2; int order1 = reduce1.reduce(cubic, SkReduceOrder::kNo_Quadratics, SkReduceOrder::kFill_Style); int order2 = reduce2.reduce(quad, SkReduceOrder::kFill_Style); if (order1 != 4) { SkDebugf("[%d] cubic order=%d\n", iIndex, order1); REPORTER_ASSERT(reporter, 0); } if (order2 != 3) { SkDebugf("[%d] quad order=%d\n", iIndex, order2); REPORTER_ASSERT(reporter, 0); } SkIntersections i; int roots = i.intersect(cubic, quad); SkASSERT(roots == quadCubicTests[index].answerCount); for (int pt = 0; pt < roots; ++pt) { double tt1 = i[0][pt]; SkDPoint xy1 = cubic.xyAtT(tt1); double tt2 = i[1][pt]; SkDPoint xy2 = quad.xyAtT(tt2); if (!xy1.approximatelyEqual(xy2)) { SkDebugf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, iIndex, pt, tt1, xy1.fX, xy1.fY, tt2, xy2.fX, xy2.fY); } REPORTER_ASSERT(reporter, xy1.approximatelyEqual(xy2)); bool found = false; for (int idx2 = 0; idx2 < quadCubicTests[index].answerCount; ++idx2) { found |= quadCubicTests[index].answers[idx2].approximatelyEqual(xy1); } REPORTER_ASSERT(reporter, found); } reporter->bumpTestCount(); } }
static void standardTestCases(skiatest::Reporter* reporter) { bool showSkipped = false; for (size_t index = 0; index < quadraticTests_count; ++index) { const SkDQuad& quad1 = quadraticTests[index][0]; SkASSERT(ValidQuad(quad1)); const SkDQuad& quad2 = quadraticTests[index][1]; SkASSERT(ValidQuad(quad2)); SkReduceOrder reduce1, reduce2; int order1 = reduce1.reduce(quad1); int order2 = reduce2.reduce(quad2); if (order1 < 3) { if (showSkipped) { SkDebugf("[%d] quad1 order=%d\n", static_cast<int>(index), order1); } } if (order2 < 3) { if (showSkipped) { SkDebugf("[%d] quad2 order=%d\n", static_cast<int>(index), order2); } } if (order1 == 3 && order2 == 3) { SkIntersections intersections; intersections.intersect(quad1, quad2); if (intersections.used() > 0) { for (int pt = 0; pt < intersections.used(); ++pt) { double tt1 = intersections[0][pt]; SkDPoint xy1 = quad1.ptAtT(tt1); double tt2 = intersections[1][pt]; SkDPoint xy2 = quad2.ptAtT(tt2); if (!xy1.approximatelyEqual(xy2)) { SkDebugf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, static_cast<int>(index), pt, tt1, xy1.fX, xy1.fY, tt2, xy2.fX, xy2.fY); REPORTER_ASSERT(reporter, 0); } } } } } }
static void testOne(skiatest::Reporter* reporter, const SkDLine& line1, const SkDLine& line2) { SkASSERT(ValidLine(line1)); SkASSERT(ValidLine(line2)); SkIntersections i; int pts = i.intersect(line1, line2); REPORTER_ASSERT(reporter, pts); REPORTER_ASSERT(reporter, pts == i.used()); check_results(reporter, line1, line2, i); if (line1[0] == line1[1] || line2[0] == line2[1]) { return; } if (line1[0].fY == line1[1].fY) { double left = SkTMin(line1[0].fX, line1[1].fX); double right = SkTMax(line1[0].fX, line1[1].fX); SkIntersections ts; ts.horizontal(line2, left, right, line1[0].fY, line1[0].fX != left); check_results(reporter, line2, line1, ts); } if (line2[0].fY == line2[1].fY) { double left = SkTMin(line2[0].fX, line2[1].fX); double right = SkTMax(line2[0].fX, line2[1].fX); SkIntersections ts; ts.horizontal(line1, left, right, line2[0].fY, line2[0].fX != left); check_results(reporter, line1, line2, ts); } if (line1[0].fX == line1[1].fX) { double top = SkTMin(line1[0].fY, line1[1].fY); double bottom = SkTMax(line1[0].fY, line1[1].fY); SkIntersections ts; ts.vertical(line2, top, bottom, line1[0].fX, line1[0].fY != top); check_results(reporter, line2, line1, ts); } if (line2[0].fX == line2[1].fX) { double top = SkTMin(line2[0].fY, line2[1].fY); double bottom = SkTMax(line2[0].fY, line2[1].fY); SkIntersections ts; ts.vertical(line1, top, bottom, line2[0].fX, line2[0].fY != top); check_results(reporter, line1, line2, ts); } }
static void PathOpsLineIntersectionTest(skiatest::Reporter* reporter) { size_t index; for (index = 0; index < coincidentTests_count; ++index) { const SkDLine& line1 = coincidentTests[index][0]; const SkDLine& line2 = coincidentTests[index][1]; testOneCoincident(reporter, line1, line2); reporter->bumpTestCount(); } for (index = 0; index < tests_count; ++index) { const SkDLine& line1 = tests[index][0]; const SkDLine& line2 = tests[index][1]; testOne(reporter, line1, line2); reporter->bumpTestCount(); } for (index = 0; index < noIntersect_count; ++index) { const SkDLine& line1 = noIntersect[index][0]; const SkDLine& line2 = noIntersect[index][1]; SkIntersections ts; int pts = ts.intersect(line1, line2); REPORTER_ASSERT(reporter, !pts); REPORTER_ASSERT(reporter, pts == ts.used()); reporter->bumpTestCount(); } }
// intersect the end of the cubic with the other. Try lines from the end to control and opposite // end to determine range of t on opposite cubic. static void intersectEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, const SkDRect& bounds2, SkIntersections& i) { SkDLine line; int t1Index = start ? 0 : 3; line[0] = cubic1[t1Index]; // don't bother if the two cubics are connnected SkTDArray<double> tVals; // OPTIMIZE: replace with hard-sized array for (int index = 0; index < 4; ++index) { if (index == t1Index) { continue; } SkDVector dxy1 = cubic1[index] - line[0]; dxy1 /= SkDCubic::gPrecisionUnit; line[1] = line[0] + dxy1; SkDRect lineBounds; lineBounds.setBounds(line); if (!bounds2.intersects(&lineBounds)) { continue; } SkIntersections local; if (!local.intersect(cubic2, line)) { continue; } for (int idx2 = 0; idx2 < local.used(); ++idx2) { double foundT = local[0][idx2]; if (approximately_less_than_zero(foundT) || approximately_greater_than_one(foundT)) { continue; } if (local.pt(idx2).approximatelyEqual(line[0])) { if (i.swapped()) { // FIXME: insert should respect swap i.insert(foundT, start ? 0 : 1, line[0]); } else { i.insert(start ? 0 : 1, foundT, line[0]); } } else { *tVals.append() = local[0][idx2]; } } } if (tVals.count() == 0) { return; } QSort<double>(tVals.begin(), tVals.end() - 1); double tMin1 = start ? 0 : 1 - LINE_FRACTION; double tMax1 = start ? LINE_FRACTION : 1; int tIdx = 0; do { int tLast = tIdx; while (tLast + 1 < tVals.count() && roughly_equal(tVals[tLast + 1], tVals[tIdx])) { ++tLast; } double tMin2 = SkTMax<double>(tVals[tIdx] - LINE_FRACTION, 0.0); double tMax2 = SkTMin<double>(tVals[tLast] + LINE_FRACTION, 1.0); int lastUsed = i.used(); intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, i); if (lastUsed == i.used()) { tMin2 = SkTMax<double>(tVals[tIdx] - (1.0 / SkDCubic::gPrecisionUnit), 0.0); tMax2 = SkTMin<double>(tVals[tLast] + (1.0 / SkDCubic::gPrecisionUnit), 1.0); intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, i); } tIdx = tLast + 1; } while (tIdx < tVals.count()); return; }
// 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"); }
void SkIntersections::cubicNearEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, const SkDRect& bounds2) { SkDLine line; int t1Index = start ? 0 : 3; double testT = (double) !start; // don't bother if the two cubics are connnected static const int kPointsInCubic = 4; // FIXME: move to DCubic, replace '4' with this static const int kMaxLineCubicIntersections = 3; SkSTArray<(kMaxLineCubicIntersections - 1) * kMaxLineCubicIntersections, double, true> tVals; line[0] = cubic1[t1Index]; // this variant looks for intersections with the end point and lines parallel to other points for (int index = 0; index < kPointsInCubic; ++index) { if (index == t1Index) { continue; } SkDVector dxy1 = cubic1[index] - line[0]; dxy1 /= SkDCubic::gPrecisionUnit; line[1] = line[0] + dxy1; SkDRect lineBounds; lineBounds.setBounds(line); if (!bounds2.intersects(&lineBounds)) { continue; } SkIntersections local; if (!local.intersect(cubic2, line)) { continue; } for (int idx2 = 0; idx2 < local.used(); ++idx2) { double foundT = local[0][idx2]; if (approximately_less_than_zero(foundT) || approximately_greater_than_one(foundT)) { continue; } if (local.pt(idx2).approximatelyEqual(line[0])) { if (swapped()) { // FIXME: insert should respect swap insert(foundT, testT, line[0]); } else { insert(testT, foundT, line[0]); } } else { tVals.push_back(foundT); } } } if (tVals.count() == 0) { return; } SkTQSort<double>(tVals.begin(), tVals.end() - 1); double tMin1 = start ? 0 : 1 - LINE_FRACTION; double tMax1 = start ? LINE_FRACTION : 1; int tIdx = 0; do { int tLast = tIdx; while (tLast + 1 < tVals.count() && roughly_equal(tVals[tLast + 1], tVals[tIdx])) { ++tLast; } double tMin2 = SkTMax(tVals[tIdx] - LINE_FRACTION, 0.0); double tMax2 = SkTMin(tVals[tLast] + LINE_FRACTION, 1.0); int lastUsed = used(); if (start ? tMax1 < tMin2 : tMax2 < tMin1) { ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); } if (lastUsed == used()) { tMin2 = SkTMax(tVals[tIdx] - (1.0 / SkDCubic::gPrecisionUnit), 0.0); tMax2 = SkTMin(tVals[tLast] + (1.0 / SkDCubic::gPrecisionUnit), 1.0); if (start ? tMax1 < tMin2 : tMax2 < tMin1) { ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); } } tIdx = tLast + 1; } while (tIdx < tVals.count()); return; }
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; }