static void standardTestCases() { size_t index; Quadratic reduce; 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 Quadratic& quad = quadraticLines[index]; order = reduceOrder(quad, reduce, kReduceOrder_TreatAsFill); if (order != 2) { printf("[%d] line quad order=%d\n", (int) index, order); } } for (index = firstQuadraticModLineTest; index < quadraticModEpsilonLines_count; ++index) { const Quadratic& quad = quadraticModEpsilonLines[index]; order = reduceOrder(quad, reduce, kReduceOrder_TreatAsFill); if (order != 3) { printf("[%d] line mod quad order=%d\n", (int) index, order); } } }
static int quadPart(const Cubic& cubic, double tStart, double tEnd, Quadratic& simple) { Cubic part; sub_divide(cubic, tStart, tEnd, part); Quadratic quad; demote_cubic_to_quad(part, quad); // FIXME: should reduceOrder be looser in this use case if quartic is going to blow up on an // extremely shallow quadratic? int order = reduceOrder(quad, simple, kReduceOrder_TreatAsFill); #if DEBUG_QUAD_PART SkDebugf("%s cubic=(%1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g) t=(%1.17g,%1.17g)\n", __FUNCTION__, cubic[0].x, cubic[0].y, cubic[1].x, cubic[1].y, cubic[2].x, cubic[2].y, cubic[3].x, cubic[3].y, tStart, tEnd); SkDebugf("%s part=(%1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g)" " quad=(%1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g)\n", __FUNCTION__, part[0].x, part[0].y, part[1].x, part[1].y, part[2].x, part[2].y, part[3].x, part[3].y, quad[0].x, quad[0].y, quad[1].x, quad[1].y, quad[2].x, quad[2].y); SkDebugf("%s simple=(%1.17g,%1.17g", __FUNCTION__, simple[0].x, simple[0].y); if (order > 1) { SkDebugf(" %1.17g,%1.17g", simple[1].x, simple[1].y); } if (order > 2) { SkDebugf(" %1.17g,%1.17g", simple[2].x, simple[2].y); } SkDebugf(")\n"); SkASSERT(order < 4 && order > 0); #endif return order; }
static void oneOffTest() { SkDebugf("%s FLT_EPSILON=%1.9g\n", __FUNCTION__, FLT_EPSILON); for (size_t index = 0; index < testSetCount; ++index) { const Quadratic& quad = testSet[index]; Quadratic reduce; SkDEBUGCODE(int result = ) reduceOrder(quad, reduce, kReduceOrder_TreatAsFill); SkASSERT(result == 3); } }
void CubicIntersection_Test() { for (size_t index = firstCubicIntersectionTest; index < tests_count; ++index) { const Cubic& cubic1 = tests[index][0]; const Cubic& cubic2 = tests[index][1]; Cubic reduce1, reduce2; int order1 = reduceOrder(cubic1, reduce1, kReduceOrder_NoQuadraticsAllowed); int order2 = reduceOrder(cubic2, reduce2, kReduceOrder_NoQuadraticsAllowed); if (order1 < 4) { printf("%s [%d] cubic1 order=%d\n", __FUNCTION__, (int) index, order1); continue; } if (order2 < 4) { printf("%s [%d] cubic2 order=%d\n", __FUNCTION__, (int) index, order2); continue; } if (implicit_matches(reduce1, reduce2)) { printf("%s [%d] coincident\n", __FUNCTION__, (int) index); continue; } Intersections tIntersections; intersect(reduce1, reduce2, tIntersections); if (!tIntersections.intersected()) { printf("%s [%d] no intersection\n", __FUNCTION__, (int) index); continue; } for (int pt = 0; pt < tIntersections.used(); ++pt) { double tt1 = tIntersections.fT[0][pt]; double tx1, ty1; xy_at_t(cubic1, tt1, tx1, ty1); double tt2 = tIntersections.fT[1][pt]; double tx2, ty2; xy_at_t(cubic2, tt2, tx2, ty2); if (!AlmostEqualUlps(tx1, tx2)) { printf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } if (!AlmostEqualUlps(ty1, ty2)) { printf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } } } }
static void standardTestCases() { for (size_t index = firstQuadIntersectionTest; index < quadraticTests_count; ++index) { const Quadratic& quad1 = quadraticTests[index][0]; const Quadratic& quad2 = quadraticTests[index][1]; Quadratic reduce1, reduce2; int order1 = reduceOrder(quad1, reduce1); int order2 = reduceOrder(quad2, reduce2); if (order1 < 3) { printf("[%d] quad1 order=%d\n", (int) index, order1); } if (order2 < 3) { printf("[%d] quad2 order=%d\n", (int) index, order2); } if (order1 == 3 && order2 == 3) { Intersections intersections, intersections2; intersect(reduce1, reduce2, intersections); intersect2(reduce1, reduce2, intersections2); SkASSERT(intersections.used() == intersections2.used()); if (intersections.intersected()) { for (int pt = 0; pt < intersections.used(); ++pt) { double tt1 = intersections.fT[0][pt]; double tx1, ty1; xy_at_t(quad1, tt1, tx1, ty1); double tt2 = intersections.fT[1][pt]; double tx2, ty2; xy_at_t(quad2, tt2, tx2, ty2); if (!approximately_equal(tx1, tx2)) { printf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } if (!approximately_equal(ty1, ty2)) { printf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } tt1 = intersections2.fT[0][pt]; SkASSERT(approximately_equal(intersections.fT[0][pt], tt1)); tt2 = intersections2.fT[1][pt]; SkASSERT(approximately_equal(intersections.fT[1][pt], tt2)); } } } } }
static void standardTestCases() { for (size_t index = 0; index < quadraticTests_count; ++index) { const Quadratic& quad1 = quadraticTests[index][0]; const Quadratic& quad2 = quadraticTests[index][1]; Quadratic reduce1, reduce2; int order1 = reduceOrder(quad1, reduce1); int order2 = reduceOrder(quad2, reduce2); if (order1 < 3) { printf("%s [%d] quad1 order=%d\n", __FUNCTION__, (int)index, order1); } if (order2 < 3) { printf("%s [%d] quad2 order=%d\n", __FUNCTION__, (int)index, order2); } if (order1 == 3 && order2 == 3) { double minT = 0; double maxT = 1; bezier_clip(reduce1, reduce2, minT, maxT); } } }
void CubicBezierClip_Test() { for (size_t index = 0; index < tests_count; ++index) { const Cubic& cubic1 = tests[index][0]; const Cubic& cubic2 = tests[index][1]; Cubic reduce1, reduce2; int order1 = reduceOrder(cubic1, reduce1, kReduceOrder_NoQuadraticsAllowed, kReduceOrder_TreatAsFill); int order2 = reduceOrder(cubic2, reduce2, kReduceOrder_NoQuadraticsAllowed, kReduceOrder_TreatAsFill); if (order1 < 4) { SkDebugf("%s [%d] cubic1 order=%d\n", __FUNCTION__, (int) index, order1); } if (order2 < 4) { SkDebugf("%s [%d] cubic2 order=%d\n", __FUNCTION__, (int) index, order2); } if (order1 == 4 && order2 == 4) { double minT = 0; double maxT = 1; bezier_clip(reduce1, reduce2, minT, maxT); } } }
void LineCubicIntersection_Test() { for (size_t index = firstLineCubicIntersectionTest; index < lineCubicTests_count; ++index) { const Cubic& cubic = lineCubicTests[index].cubic; const _Line& line = lineCubicTests[index].line; Cubic reduce1; _Line reduce2; int order1 = reduceOrder(cubic, reduce1, kReduceOrder_NoQuadraticsAllowed); int order2 = reduceOrder(line, reduce2); if (order1 < 4) { printf("[%d] cubic order=%d\n", (int) index, order1); } if (order2 < 2) { printf("[%d] line order=%d\n", (int) index, order2); } if (order1 == 4 && order2 == 2) { double range1[2], range2[2]; int roots = intersect(reduce1, reduce2, range1, range2); for (int pt = 0; pt < roots; ++pt) { double tt1 = range1[pt]; double tx1, ty1; xy_at_t(cubic, tt1, tx1, ty1); double tt2 = range2[pt]; double tx2, ty2; xy_at_t(line, tt2, tx2, ty2); if (!approximately_equal(tx1, tx2)) { printf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } if (!approximately_equal(ty1, ty2)) { printf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n", __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); } } } } }
bool intersect(double minT1, double maxT1, double minT2, double maxT2) { Quadratic smaller, larger; // FIXME: carry last subdivide and reduceOrder result with quad sub_divide(quad1, minT1, maxT1, intersections.swapped() ? larger : smaller); sub_divide(quad2, minT2, maxT2, intersections.swapped() ? smaller : larger); Quadratic smallResult; if (reduceOrder(smaller, smallResult) <= 2) { Quadratic largeResult; if (reduceOrder(larger, largeResult) <= 2) { double smallT[2], largeT[2]; const _Line& smallLine = (const _Line&) smallResult; const _Line& largeLine = (const _Line&) largeResult; // FIXME: this doesn't detect or deal with coincident lines if (!::intersect(smallLine, largeLine, smallT, largeT)) { return false; } if (intersections.swapped()) { smallT[0] = interp(minT2, maxT2, smallT[0]); largeT[0] = interp(minT1, maxT1, largeT[0]); } else { smallT[0] = interp(minT1, maxT1, smallT[0]); largeT[0] = interp(minT2, maxT2, largeT[0]); } intersections.add(smallT[0], largeT[0]); return true; } } double minT, maxT; if (!bezier_clip(smaller, larger, minT, maxT)) { if (minT == maxT) { if (intersections.swapped()) { minT1 = (minT1 + maxT1) / 2; minT2 = interp(minT2, maxT2, minT); } else { minT1 = interp(minT1, maxT1, minT); minT2 = (minT2 + maxT2) / 2; } intersections.add(minT1, minT2); return true; } return false; } int split; if (intersections.swapped()) { double newMinT1 = interp(minT1, maxT1, minT); double newMaxT1 = interp(minT1, maxT1, maxT); split = (newMaxT1 - newMinT1 > (maxT1 - minT1) * tClipLimit) << 1; #define VERBOSE 0 #if VERBOSE printf("%s d=%d s=%d new1=(%g,%g) old1=(%g,%g) split=%d\n", __FUNCTION__, depth, splits, newMinT1, newMaxT1, minT1, maxT1, split); #endif minT1 = newMinT1; maxT1 = newMaxT1; } else { double newMinT2 = interp(minT2, maxT2, minT); double newMaxT2 = interp(minT2, maxT2, maxT); split = newMaxT2 - newMinT2 > (maxT2 - minT2) * tClipLimit; #define VERBOSE 0 #if VERBOSE printf("%s d=%d s=%d new2=(%g,%g) old2=(%g,%g) split=%d\n", __FUNCTION__, depth, splits, newMinT2, newMaxT2, minT2, maxT2, split); #endif minT2 = newMinT2; maxT2 = newMaxT2; } return chop(minT1, maxT1, minT2, maxT2, split); }
// this flavor approximates the cubics with quads to find the intersecting ts // OPTIMIZE: if this strategy proves successful, the quad approximations, or the ts used // to create the approximations, could be stored in the cubic segment // FIXME: this strategy needs to intersect the convex hull on either end with the opposite to // account for inset quadratics that cause the endpoint intersection to avoid detection // the segments can be very short -- the length of the maximum quadratic error (precision) // FIXME: this needs to recurse on itself, taking a range of T values and computing the new // t range ala is linear inner. The range can be figured by taking the dx/dy and determining // the fraction that matches the precision. That fraction is the change in t for the smaller cubic. static bool intersect2(const Cubic& cubic1, double t1s, double t1e, const Cubic& cubic2, double t2s, double t2e, double precisionScale, Intersections& i) { Cubic c1, c2; sub_divide(cubic1, t1s, t1e, c1); sub_divide(cubic2, t2s, t2e, c2); SkTDArray<double> ts1; cubic_to_quadratics(c1, calcPrecision(c1) * precisionScale, ts1); SkTDArray<double> ts2; cubic_to_quadratics(c2, calcPrecision(c2) * precisionScale, ts2); double t1Start = t1s; int ts1Count = ts1.count(); for (int i1 = 0; i1 <= ts1Count; ++i1) { const double tEnd1 = i1 < ts1Count ? ts1[i1] : 1; const double t1 = t1s + (t1e - t1s) * tEnd1; Cubic part1; sub_divide(cubic1, t1Start, t1, part1); Quadratic q1; demote_cubic_to_quad(part1, q1); // start here; // should reduceOrder be looser in this use case if quartic is going to blow up on an // extremely shallow quadratic? Quadratic s1; int o1 = reduceOrder(q1, s1); double t2Start = t2s; int ts2Count = ts2.count(); for (int i2 = 0; i2 <= ts2Count; ++i2) { const double tEnd2 = i2 < ts2Count ? ts2[i2] : 1; const double t2 = t2s + (t2e - t2s) * tEnd2; Cubic part2; sub_divide(cubic2, t2Start, t2, part2); Quadratic q2; demote_cubic_to_quad(part2, q2); Quadratic s2; double o2 = reduceOrder(q2, s2); Intersections locals; if (o1 == 3 && o2 == 3) { intersect2(q1, q2, locals); } else if (o1 <= 2 && o2 <= 2) { locals.fUsed = intersect((const _Line&) s1, (const _Line&) s2, locals.fT[0], locals.fT[1]); } else if (o1 == 3 && o2 <= 2) { intersect(q1, (const _Line&) s2, locals); } else { SkASSERT(o1 <= 2 && o2 == 3); intersect(q2, (const _Line&) s1, locals); for (int s = 0; s < locals.fUsed; ++s) { SkTSwap(locals.fT[0][s], locals.fT[1][s]); } } for (int tIdx = 0; tIdx < locals.used(); ++tIdx) { double to1 = t1Start + (t1 - t1Start) * locals.fT[0][tIdx]; double to2 = t2Start + (t2 - t2Start) * locals.fT[1][tIdx]; // if the computed t is not sufficiently precise, iterate _Point p1, p2; xy_at_t(cubic1, to1, p1.x, p1.y); xy_at_t(cubic2, to2, p2.x, p2.y); if (p1.approximatelyEqual(p2)) { i.insert(i.swapped() ? to2 : to1, i.swapped() ? to1 : to2); } else { double dt1, dt2; computeDelta(cubic1, to1, (t1e - t1s), cubic2, to2, (t2e - t2s), dt1, dt2); double scale = precisionScale; if (dt1 > 0.125 || dt2 > 0.125) { scale /= 2; SkDebugf("%s scale=%1.9g\n", __FUNCTION__, scale); } #if SK_DEBUG ++debugDepth; assert(debugDepth < 10); #endif i.swap(); intersect2(cubic2, SkTMax(to2 - dt2, 0.), SkTMin(to2 + dt2, 1.), cubic1, SkTMax(to1 - dt1, 0.), SkTMin(to1 + dt1, 1.), scale, i); i.swap(); #if SK_DEBUG --debugDepth; #endif } } t2Start = t2; } t1Start = t1; } return i.intersected(); }
void CubicReduceOrder_Test() { size_t index; Cubic reduce; int order; enum { RunAll, RunPointDegenerates, RunNotPointDegenerates, RunLines, RunNotLines, RunModEpsilonLines, RunLessEpsilonLines, RunNegEpsilonLines, RunQuadraticLines, RunQuadraticModLines, RunComputedLines, RunNone } run = RunAll; int firstTestIndex = 0; #if 0 run = RunComputedLines; firstTestIndex = 18; #endif int firstPointDegeneratesTest = run == RunAll ? 0 : run == RunPointDegenerates ? firstTestIndex : SK_MaxS32; int firstNotPointDegeneratesTest = run == RunAll ? 0 : run == RunNotPointDegenerates ? firstTestIndex : SK_MaxS32; int firstLinesTest = run == RunAll ? 0 : run == RunLines ? firstTestIndex : SK_MaxS32; int firstNotLinesTest = run == RunAll ? 0 : run == RunNotLines ? firstTestIndex : SK_MaxS32; int firstModEpsilonTest = run == RunAll ? 0 : run == RunModEpsilonLines ? firstTestIndex : SK_MaxS32; int firstLessEpsilonTest = run == RunAll ? 0 : run == RunLessEpsilonLines ? firstTestIndex : SK_MaxS32; int firstNegEpsilonTest = run == RunAll ? 0 : run == RunNegEpsilonLines ? firstTestIndex : SK_MaxS32; int firstQuadraticLineTest = run == RunAll ? 0 : run == RunQuadraticLines ? firstTestIndex : SK_MaxS32; int firstQuadraticModLineTest = run == RunAll ? 0 : run == RunQuadraticModLines ? firstTestIndex : SK_MaxS32; int firstComputedLinesTest = run == RunAll ? 0 : run == RunComputedLines ? firstTestIndex : SK_MaxS32; for (index = firstPointDegeneratesTest; index < pointDegenerates_count; ++index) { const Cubic& cubic = pointDegenerates[index]; order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (order != 1) { SkDebugf("[%d] pointDegenerates order=%d\n", (int) index, order); } } for (index = firstNotPointDegeneratesTest; index < notPointDegenerates_count; ++index) { const Cubic& cubic = notPointDegenerates[index]; order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (order == 1) { SkDebugf("[%d] notPointDegenerates order=%d\n", (int) index, order); } } for (index = firstLinesTest; index < lines_count; ++index) { const Cubic& cubic = lines[index]; order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (order != 2) { SkDebugf("[%d] lines order=%d\n", (int) index, order); } } for (index = firstNotLinesTest; index < notLines_count; ++index) { const Cubic& cubic = notLines[index]; order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (order == 2) { SkDebugf("[%d] notLines order=%d\n", (int) index, order); } } for (index = firstModEpsilonTest; index < modEpsilonLines_count; ++index) { const Cubic& cubic = modEpsilonLines[index]; order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (order == 2) { SkDebugf("[%d] line mod by epsilon order=%d\n", (int) index, order); } } for (index = firstLessEpsilonTest; index < lessEpsilonLines_count; ++index) { const Cubic& cubic = lessEpsilonLines[index]; order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (order != 2) { SkDebugf("[%d] line less by epsilon/2 order=%d\n", (int) index, order); } } for (index = firstNegEpsilonTest; index < negEpsilonLines_count; ++index) { const Cubic& cubic = negEpsilonLines[index]; order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (order != 2) { SkDebugf("[%d] line neg by epsilon/2 order=%d\n", (int) index, order); } } for (index = firstQuadraticLineTest; index < quadraticLines_count; ++index) { const Quadratic& quad = quadraticLines[index]; Cubic cubic; quad_to_cubic(quad, cubic); order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (order != 2) { SkDebugf("[%d] line quad order=%d\n", (int) index, order); } } for (index = firstQuadraticModLineTest; index < quadraticModEpsilonLines_count; ++index) { const Quadratic& quad = quadraticModEpsilonLines[index]; Cubic cubic; quad_to_cubic(quad, cubic); order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (order != 3) { SkDebugf("[%d] line mod quad order=%d\n", (int) index, order); } } // test if computed line end points are valid for (index = firstComputedLinesTest; index < lines_count; ++index) { const Cubic& cubic = lines[index]; bool controlsInside = controls_inside(cubic); order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill); if (reduce[0].x == reduce[1].x && reduce[0].y == reduce[1].y) { SkDebugf("[%d] line computed ends match order=%d\n", (int) index, order); } if (controlsInside) { if ( (reduce[0].x != cubic[0].x && reduce[0].x != cubic[3].x) || (reduce[0].y != cubic[0].y && reduce[0].y != cubic[3].y) || (reduce[1].x != cubic[0].x && reduce[1].x != cubic[3].x) || (reduce[1].y != cubic[0].y && reduce[1].y != cubic[3].y)) { SkDebugf("[%d] line computed ends order=%d\n", (int) index, order); } } else { // binary search for extrema, compare against actual results // while a control point is outside of bounding box formed by end points, split _Rect bounds = {DBL_MAX, DBL_MAX, -DBL_MAX, -DBL_MAX}; find_tight_bounds(cubic, bounds); if ( (!AlmostEqualUlps(reduce[0].x, bounds.left) && !AlmostEqualUlps(reduce[0].x, bounds.right)) || (!AlmostEqualUlps(reduce[0].y, bounds.top) && !AlmostEqualUlps(reduce[0].y, bounds.bottom)) || (!AlmostEqualUlps(reduce[1].x, bounds.left) && !AlmostEqualUlps(reduce[1].x, bounds.right)) || (!AlmostEqualUlps(reduce[1].y, bounds.top) && !AlmostEqualUlps(reduce[1].y, bounds.bottom))) { SkDebugf("[%d] line computed tight bounds order=%d\n", (int) index, order); } } } }