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);
    }
}
Exemple #4
0
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);
}
Exemple #10
0
// 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);
            }

        }
    }
}