
// BakeIndexedStrokedPath.cpp - implementation of baking a stroked path for polar stroking

#include "BakeIndexedStrokedPath.hpp"

#include "common/CommonStrokeTessellatorPrivate.hpp"  // for UNUSED

#include <Cg/all.hpp>
#include <Cg/any.hpp>
#include <Cg/distance.hpp>
#include <Cg/length.hpp>
//#include <Cg/lerp.hpp>  // This has problems with Visual Studio 2008
#define lerp(a,b,t) ((a) + (t)*((b)-(a)))
#include <Cg/radians.hpp>
#include <Cg/iostream.hpp>
#include <Cg/isfinite.hpp>

#include "cg4cpp_tangent.hpp"

void BakeIndexedStrokedPath::addCap(CapPlacement placement, GLuint anchor, GLuint heading)
{
    if (include_caps) {
        StrokeLink cap;
        cap.ndx[0] = anchor;
        cap.ndx[1] = 0;  // is join or cap
        switch (placement) {
        case INITIAL_CAP:
            cap.ndx[2] = 0;
            cap.ndx[3] = 0;
            break;
        case TERMINAL_CAP:
            cap.ndx[2] = anchor;
            cap.ndx[3] = 0;
            break;
        case INITIAL_DASH_CAP:
            cap.ndx[2] = 0;
            cap.ndx[3] = anchor;
            break;
        case TERMINAL_DASH_CAP:
            cap.ndx[2] = anchor;
            cap.ndx[3] = anchor;
            break;
        }
        cap.ndx[4] = heading;
        cap.ndx[5] = 0;  // is cap
        link.push_back(cap);
    }
}

void BakeIndexedStrokedPath::beginPath(PathPtr &p)
{
    const size_t num_commands = p->getCmdSize();
    const size_t num_coords = p->getCoordSize();
    if (verbose) {
        printf("begin path\n");
        printf("  num_commands = %d\n", int(num_commands));
        printf("  num_coords = %d\n", int(num_coords));
    }
    coord.reserve(num_coords);  // Could be an overestimate when sequential redundant control points.
    const size_t estimated_cubics_needed = 1 + num_commands*2 + 1;  // initial cap + every segments with its join or terminal cap.
    link.reserve(estimated_cubics_needed);
    cp_push_back(float2(0,0));  // Push "special" vertex index zero, used to indicate joins/caps/quadratics.
    needs_cubic_stabilization = false;
}

void BakeIndexedStrokedPath::moveTo(const float2 p[2])
{
    if (verbose) {
        printf("MoveTo\n");
        std::cout << " p[0] = " << p[0] << " (point before moveTo)" << std::endl;
        std::cout << " p[1] = " << p[1] << " (point to moveTo)" << std::endl;
    }

    if (moveTo_index != link.size()) {
        addCap(TERMINAL_CAP, link.back().ndx[3], link.back().ndx[5]);
    }

    if (needs_initial_cap != BOGUS_INDEX) {
        const StrokeLink &s = link[needs_initial_cap];
        addCap(INITIAL_CAP, s.ndx[0], s.ndx[4]);
        needs_initial_cap = BOGUS_INDEX;
    } else {
        // closed or single point contour
    }
    moveTo_index = link.size();

    if (dashing_state.Dashing()) {
        if (dash_offset_reset == DashStyle::MOVETO_RESETS) {
            dashing_state = initial_dashing_state;
        } else {
            assert(dash_offset_reset == DashStyle::MOVETO_CONTINUES);
            initial_dashing_state.copyDashOnState(dashing_state);
        }
    }
}

void BakeIndexedStrokedPath::addPseudoCusp()
{
    const vector<StrokeLink>::iterator iter = link.end();

    StrokeLink& line1 = *(iter-2);  // Second to backmost link.

    // Check if this link is actually a recently added cap.
    if (line1.ndx[5] == 0) {
        // Yes, it's a cap so move back another link and expect a line there.
        // Double check the cap assumption.
        assert(line1.ndx[0] == line1.ndx[3]);
        assert(link.size() >= 3);
        line1 = *(iter-3);  // Third to backmost link.
    }

    // Confirm line1 link really is a line segment.
    assert(line1.ndx[0] == line1.ndx[1]);
    assert(line1.ndx[0] == line1.ndx[5]);
    assert(line1.ndx[3] == line1.ndx[2]);
    assert(line1.ndx[3] == line1.ndx[4]);

    StrokeLink &line2 = *(iter-1);  // Back link, which gets overwritten later so non-const!

    // Confirm line2 link really is a line segment.
    assert(line2.ndx[0] == line2.ndx[1]);
    assert(line2.ndx[0] == line2.ndx[5]);
    assert(line2.ndx[3] == line2.ndx[2]);
    assert(line2.ndx[3] == line2.ndx[4]);

    // And the two lines are joined.
    assert(line1.ndx[3] == line2.ndx[0]);

    // Add degenerate cubic (rather than a join!) so a distinct start and
    // end tangent can be specified for a single point to form effectively
    // a round pseudo-join.
    // We avoid addJoin as its joins are subject to join style.
    const GLuint cusp_ndx = line2.ndx[0];
    StrokeLink cusp;
    cusp.ndx[0] = cusp_ndx;
    cusp.ndx[1] = cusp_ndx;
    cusp.ndx[2] = cusp_ndx;
    cusp.ndx[3] = cusp_ndx;
    cusp.ndx[4] = line1.ndx[0];  // start-point of first line
    cusp.ndx[5] = line2.ndx[3];  // end-point of second line

    // Tricky: push line2 value to back (momentarily duplicating it), then overwrite line2 reference with cusp.
    // This make sure line2, rather than the cusp, is at the end so the next cap/join is correct.
    link.push_back(line2);
    line2 = cusp;
}

void BakeIndexedStrokedPath::addJoin(const StrokeLink &s)
{
    assert(join_needed);
    const StrokeLink &prev = link.back();
    const GLuint join_ndx = s.ndx[0];
    assert(coord[join_ndx+0] == coord[prev.ndx[3]+0]);  // X
    assert(coord[join_ndx+1] == coord[prev.ndx[3]+1]);  // Y
    const GLuint prev_direction_ndx = prev.ndx[5];
    const GLuint curr_direction_ndx = s.ndx[4];
    StrokeLink join;
    join.ndx[0] = join_ndx;
    // Vertex ID zero is a special marker.
    // For the 2nd vertex of a patch, it indicates a join.
    join.ndx[1] = 0;
    join.ndx[2] = join_ndx;
    join.ndx[3] = join_ndx;
    join.ndx[4] = prev_direction_ndx;
    join.ndx[5] = curr_direction_ndx;
    link.push_back(join);
}

void BakeIndexedStrokedPath::possiblyAddInitialDashCap(const StrokeLink& s)
{
    if (needs_initial_dash_cap) {
        // Initial dash cap.
        addCap(INITIAL_DASH_CAP, s.ndx[0], s.ndx[4]);
    } else {
        // First time doesn't need dash cap since end cap applies.
        needs_initial_dash_cap = true;
    }
}

void BakeIndexedStrokedPath::addStrokeLink(const StrokeLink& s)
{
    if (needs_initial_cap == BOGUS_INDEX) {
        needs_initial_cap = link.size();
        possiblyAddInitialDashCap(s);
    } else {
        if (join_needed) {
            addJoin(s);
        } else {
            possiblyAddInitialDashCap(s);
        }
    }
    link.push_back(s);
}

void BakeIndexedStrokedPath::addInitialControlPoint(const float2& p)
{
    if (coord.size() <= 2) {
        assert(coord.size() == 2);  // Expect special vertex to be pushed.
        cp_push_back(p);
    }
}

void BakeIndexedStrokedPath::addLineToLink(const float2 p[2])
{
    // Are the end-points identical?
    if (all(p[0] == p[1])) {
        // Yes, skip
    } else {
        // No, treat a line as a (degenerate) cubic with 4 points p[0],p[0],p[1],p[1]
        addInitialControlPoint(p[0]);
        if (any(cp_back() != p[0])) {
            cp_push_back(p[0]);
            assert(all(cp_back() == p[0]));
        }
        StrokeLink s;
        const GLuint start_ndx = GLuint(coord.size()-2);
        s.ndx[0] = start_ndx;
        s.ndx[1] = start_ndx;
        s.ndx[5] = start_ndx;
        cp_push_back(p[1]);
        const GLuint end_ndx = GLuint(coord.size()-2);
        s.ndx[2] = end_ndx;
        s.ndx[3] = end_ndx;
        s.ndx[4] = end_ndx;
        addStrokeLink(s);
    }
}

void BakeIndexedStrokedPath::dashedLineTo(const double2 p[2])
{
    const double2 &p0 = p[0],
                  &p1 = p[1],
                  tangent = p1-p0;
    const double tangent_length = length(tangent);

    if (tangent_length != 0) {
        double2 ccp[2] = { p0, p1 };

        const double segment_arc_length = tangent_length;
        double arc_length_left = segment_arc_length;
        double segment_distance_into_pattern = dashing_state.distanceIntoPattern();
        double segment_distance_left = dash_array[dashing_state.CurrentSegment()] - segment_distance_into_pattern;
        // While this line segment has more arc length left, keep dashing...
        while (segment_distance_left < arc_length_left) {
            if (verbose) {
                printf("current_segment = %d, dash_on = %d\n",
                    int(dashing_state.CurrentSegment()), dashing_state.DashOn());
                printf("segment_distance_into_pattern = %f, segment_distance_left = %f\n",
                    segment_distance_into_pattern, segment_distance_left);
            }
            const double t = segment_distance_left/arc_length_left;
            const double2 mid = lerp(ccp[0], ccp[1], t);

            if (dashing_state.DashOn()) {
                if (needs_terminal_dash_cap != BOGUS_INDEX) {
                    const StrokeLink &s = link[needs_terminal_dash_cap];
                    // Terminal dash cap.
                    addCap(TERMINAL_DASH_CAP, s.ndx[3], s.ndx[5]);
                }
                const float2 flt_p[2] = { float2(ccp[0]), float2(mid) };
                addLineToLink(flt_p);
                needs_terminal_dash_cap = link.size()-1;
                join_needed = false;
            }
            // Update ccp with "second half" of divided line segment
            ccp[0] = mid;
            dashing_state.advancePatternElement(dash_array);
            arc_length_left -= segment_distance_left;
            segment_distance_into_pattern = 0;
            segment_distance_left = dash_array[dashing_state.CurrentSegment()];
        }
        // Finish this line segment (it ends before the current dash segement ends)
        dashing_state.advanceDistance(arc_length_left);
        if (dashing_state.DashOn()) {
            if (needs_terminal_dash_cap != BOGUS_INDEX) {
                const StrokeLink &s = link[needs_terminal_dash_cap];
                // Terminal dash cap.
                addCap(TERMINAL_DASH_CAP, s.ndx[3], s.ndx[5]);
            }
            const float2 flt_p[2] = { float2(ccp[0]), float2(ccp[1]) };
            const size_t size_prior_to_final_line = link.size();
            addLineToLink(flt_p);
            if (link.size() > size_prior_to_final_line) {
                needs_terminal_dash_cap = size_prior_to_final_line;
                needs_terminal_dash_cap = BOGUS_INDEX;
                join_needed = true;
            }
        }
    }
}

void BakeIndexedStrokedPath::lineToHelper(const float2 p[2])
{
    if (dashing_state.Dashing()) {
        const double2 pd[] = { double2(p[0]), double2(p[1]) };
        dashedLineTo(pd);
    } else {
        addLineToLink(p);
        join_needed = true;
    }
}


void BakeIndexedStrokedPath::lineTo(const float2 p[2])
{
    if (verbose) {
        printf("LineTo\n");
        std::cout << " p[0] = " << p[0] << std::endl;
        std::cout << " p[1] = " << p[1] << std::endl;
    }
    lineToHelper(p);
}

void BakeIndexedStrokedPath::commonQuadraticConicIndexProcessing(StrokeLink &s, size_t before_size)
{
    // Move final vertex to third vertex.
    // XXX revisit this.  Maybe better to have s.ndx[3] be zero for a quadratic??
    s.ndx[3] = s.ndx[2];
    s.ndx[2] = 0;
    if (s.ndx[0] == s.ndx[1]) {
        if (s.ndx[0] == s.ndx[3]) {
            // All indices the same!
            // Reset coord vector to size before any push_backs from this cubicCurveTo attempt.
            coord.resize(before_size);
            // Bail without pushing s.
            return;
        } else {
            s.ndx[4] = s.ndx[3];
        }
    } else {
        s.ndx[4] = s.ndx[1];
    }
    if (s.ndx[3] == s.ndx[1]) {
        // All indices the same should have been detected already!
        assert(s.ndx[3] != s.ndx[0]);
        s.ndx[5] = s.ndx[0];
    } else {
        s.ndx[5] = s.ndx[1];
    }
    addStrokeLink(s);
}

void BakeIndexedStrokedPath::addQuadraticCurveTo(const float2 p[3])
{
    if (!native_quadratics) {
        // Promote quadratic to a cubic (masquerading as a quadratic).
        const float2 cubic_p[4] = {
            p[0],
            (2*p[1]+p[0])/3,
            (2*p[1]+p[2])/3,
            p[2]
        };
        addCubicCurveTo(cubic_p);
        return;
    }

    const size_t before_size = coord.size();
    addInitialControlPoint(p[0]);
    StrokeLink s;
    for (int i=0; i<3; i++) {
        if (all(cp_back() == p[i])) {
            s.ndx[i] = GLuint(coord.size()-2);
        } else {
            s.ndx[i] = GLuint(coord.size());
            cp_push_back(p[i]);
            assert(all(cp_back() == p[i]));
        }
    }
    commonQuadraticConicIndexProcessing(s, before_size);
}

double BakeIndexedStrokedPath::segmentArcLength() const
{
    if (progress.size() > 0) {
        return progress.back().cummulative_arc_length;
    } else {
        return 0;
    }
}

void BakeIndexedStrokedPath::dashedQuadraticCurveTo(const double2 cp[3])
{
    // Original quadratic A,B,C
    const double2 A = cp[0],
                  B = cp[1],
                  C = cp[2];

    const float2 quadratic[3] = {
        float2(cp[0]),
        float2(cp[1]),
        float2(cp[2]),
    };
    progress.clear();
    stroker.tessellateQuadraticPosition(nextRibSegmentFunc, this, quadratic);

    double arc_length_progress = 0;

    const double segment_arc_length = segmentArcLength();
    double arc_length_left = segment_arc_length;
    double segment_distance_into_pattern = dashing_state.distanceIntoPattern();
    double segment_distance_left = dash_array[dashing_state.CurrentSegment()] - segment_distance_into_pattern;

    double start_dash_t = 0;
    double end_dash_t = 0;

    arc_length_progress = segment_distance_left;
    size_t initial_index = 0;  // Initially binary search entire progress vector.

    // While this line segment has more arc length left, keep dashing...
    while (arc_length_progress < segment_arc_length) {
        if (verbose) {
            printf("current_segment = %d, dash_on = %d\n",
                int(dashing_state.CurrentSegment()), dashing_state.DashOn());
            printf("segment_distance_into_pattern = %f, segment_distance_left = %f\n",
                segment_distance_into_pattern, segment_distance_left);
        }
        end_dash_t = tForDistanceIntoSegment(arc_length_progress, initial_index);
        assert(start_dash_t <= end_dash_t);

        if (dashing_state.DashOn() && end_dash_t > 0) {
            // Apply de Casteljau's algorithm for cubic chopping over the range [0,t1]
            const double2 ab = lerp(A, B, end_dash_t),
                          bc = lerp(B, C, end_dash_t),
                          abc = lerp(ab, bc, end_dash_t);

            assert(end_dash_t != 0);
            double amended_start_dash_t = start_dash_t/end_dash_t;

            // Apply de Casteljau's algorithm for cubic chopping over the range [0,t1]
            const double2 ab2 = lerp(A, ab, amended_start_dash_t),
                          bc2 = lerp(ab, abc, amended_start_dash_t),
                          abc2 = lerp(ab2, bc2, amended_start_dash_t);

            const float2 split_quadratic[3] = {
                float2(abc2),
                float2(bc2),
                float2(abc)
            };

            if (needs_terminal_dash_cap != BOGUS_INDEX) {
                const StrokeLink &s = link[needs_terminal_dash_cap];
                // Terminal dash cap.
                addCap(TERMINAL_DASH_CAP, s.ndx[3], s.ndx[5]);
            }
            addQuadraticCurveTo(split_quadratic);
            needs_terminal_dash_cap = link.size()-1;
            join_needed = false;
        }

        assert(start_dash_t <= end_dash_t);
        start_dash_t = end_dash_t;

        dashing_state.advancePatternElement(dash_array);
        arc_length_left -= segment_distance_left;

        segment_distance_into_pattern = 0;
        segment_distance_left = dash_array[dashing_state.CurrentSegment()];

        arc_length_progress += segment_distance_left;
    }

    // Finish this line segment (it ends before the current dash segement ends)
    dashing_state.advanceDistance(arc_length_left);
    if (dashing_state.DashOn()) {
        if (needs_terminal_dash_cap != BOGUS_INDEX) {
            const StrokeLink &s = link[needs_terminal_dash_cap];
            // Terminal dash cap.
            addCap(TERMINAL_DASH_CAP, s.ndx[3], s.ndx[5]);
        }
        // Apply de Casteljau's algorithm for cubic chopping over the range [0,t1]
        const double2 ab = lerp(A, B, end_dash_t),
                      bc = lerp(B, C, end_dash_t),
                      abc = lerp(ab, bc, end_dash_t);

        const float2 split_quadratic[4] = {
            float2(abc),
            float2(bc),
            float2(C),
        };

        const size_t size_prior_to_final_quadratic = link.size();
        addQuadraticCurveTo(split_quadratic);
        if (link.size() > size_prior_to_final_quadratic) {
            needs_terminal_dash_cap = size_prior_to_final_quadratic;
            needs_terminal_dash_cap = BOGUS_INDEX;
            join_needed = true;
        }
    }
}

void BakeIndexedStrokedPath::quadraticCurveToHelper(const float2 p[3])
{
    if (dashing_state.Dashing()) {
        const double2 pd[] = { double2(p[0]), double2(p[1]), double2(p[2]) };
        dashedQuadraticCurveTo(pd);
    } else {
        addQuadraticCurveTo(p);
        join_needed = true;
    }
}

void BakeIndexedStrokedPath::quadraticCurveTo(const float2 p[3])
{
    if (verbose) {
        printf("QuadraticCurveTo\n");
        std::cout << " p[0] = " << p[0] << std::endl;
        std::cout << " p[1] = " << p[1] << std::endl;
        std::cout << " p[2] = " << p[2] << std::endl;
    }
    quadraticCurveToHelper(p);
}

void BakeIndexedStrokedPath::addTinyWConicCurveTo(const float2 p[3])
{
    addInitialControlPoint(p[0]);
    const GLuint cusp1_ndx = GLuint(coord.size()-2);

    cp_push_back(p[1]);
    const GLuint p1_ndx = GLuint(coord.size()-2);

    const float2 relfect_p2_through_p0 = 2*p[0] - p[2];
    const float2 relfect_p0_through_p2 = 2*p[2] - p[0];

    cp_push_back(relfect_p2_through_p0);
    const GLuint relfect_p2_through_p0_ndx = GLuint(coord.size()-2);
    cp_push_back(relfect_p0_through_p2);
    const GLuint relfect_p0_through_p2_ndx = GLuint(coord.size()-2);

    cp_push_back(p[2]);
    const GLuint cusp2_ndx = GLuint(coord.size()-2);

    StrokeLink cusp1;
    cusp1.ndx[0] = cusp1_ndx;
    cusp1.ndx[1] = cusp1_ndx;
    cusp1.ndx[2] = cusp1_ndx;
    cusp1.ndx[3] = cusp1_ndx;
    cusp1.ndx[4] = p1_ndx;
    cusp1.ndx[5] = relfect_p2_through_p0_ndx;
    addStrokeLink(cusp1);  // Ensures joins and caps handled.
    join_needed = true;

    StrokeLink line;
    line.ndx[0] = cusp1_ndx;
    line.ndx[1] = cusp1_ndx;
    line.ndx[2] = cusp2_ndx;
    line.ndx[3] = cusp2_ndx;
    line.ndx[4] = cusp2_ndx;
    line.ndx[5] = cusp1_ndx;
    link.push_back(line);

    StrokeLink cusp2;
    cusp2.ndx[0] = cusp2_ndx;
    cusp2.ndx[1] = cusp2_ndx;
    cusp2.ndx[2] = cusp2_ndx;
    cusp2.ndx[3] = cusp2_ndx;
    cusp2.ndx[4] = relfect_p0_through_p2_ndx;
    cusp2.ndx[5] = p1_ndx;
    link.push_back(cusp2);
}

void BakeIndexedStrokedPath::addHugeWConicCurveTo(const float2 p[3])
{
    const size_t before_size = link.size();
    const float2* line1 = &p[0];
    addLineToLink(line1);
    size_t save_moveTo_index = moveTo_index; {  // Save moveTo_index...
        const size_t mid_size = link.size();

        // This avoids addJoin adding joins temporarily.
        moveTo_index = mid_size;
        assert(!join_needed);  // Confirm we don't need joins now.

        const float2* line2 = &p[1];
        addLineToLink(line2);
        const size_t after_size = link.size();
        // Were at least the two line segments added?
        if (before_size < mid_size && mid_size < after_size) {
            // Yes, add a round junction...
            assert(after_size - before_size >= 2);  // At least the two segments were added
            assert(after_size - before_size <= 3);  // But also a cap might be added, but nothing else
            addPseudoCusp();
        }
    } moveTo_index = save_moveTo_index;  // ...Restore moveTo_index.
}

void BakeIndexedStrokedPath::addConicCurveTo(const float2 p[3], float w)
{
    // Technically, negative W conics are defined but violate the convex
    // hull property and will project through infinity.
    // XXX NV_path_rendering treats non-positive conics as lines; this
    // should change to respect the start & end tangent.
    if (w == 0) {
        const float2 line[2] = { p[0], p[2] };
        addLineToLink(line);
        return;
    }
    if (w == 1) {
        addQuadraticCurveTo(p);
        return;
    }
    const float huge_w = float(1048576);  // 2^20, approximately million
    if (w >= huge_w) {
        addHugeWConicCurveTo(p);
        return;
    }
    const float tiny_w = 1/float(1048576);  // 1/2^20, approximately one million
    if (0 < w && w <= tiny_w) {
        addTinyWConicCurveTo(p);
        return;
    }

    has_nontrivial_conic = true;
    const size_t before_size = coord.size();
    addInitialControlPoint(p[0]);
    StrokeLink s;
    // For each control point...
    const int coords_per_control_point = 2;

    if (all(cp_back() == p[0])) {
        s.ndx[0] = GLuint(coord.size()-coords_per_control_point);
    } else {
        s.ndx[0] = GLuint(coord.size());
        cp_push_back(p[0]);
        assert(all(cp_back() == p[0]));
    }

    coord.push_back(0);  // Push zero so we can get to an odd element index.
    s.ndx[1] = GLuint(coord.size());
    cp_push_back(w*p[1]);
    coord.push_back(w);
    s.ndx[2] = 0;

    s.ndx[2] = GLuint(coord.size());
    cp_push_back(p[2]);
    assert(all(cp_back() == p[2]));

    assert(s.ndx[0] ^ 1);  // First control point has even vertex index.
    assert(coord[s.ndx[0]+0] == p[0].x);
    assert(coord[s.ndx[0]+1] == p[0].y);

    assert(s.ndx[1] & 1);  // Second control point has odd vertex index.
    // Interpolating control point should be (x,y,w)
    assert(coord[s.ndx[1]+0] == w*p[1].x);
    assert(coord[s.ndx[1]+1] == w*p[1].y);
    assert(coord[s.ndx[1]+2] == w);

    assert(s.ndx[2] ^ 1);  // Third control point has even vertex index.
    assert(coord[s.ndx[2]+0] == p[2].x);
    assert(coord[s.ndx[2]+1] == p[2].y);

    commonQuadraticConicIndexProcessing(s, before_size);
    // Early return occurs for parabolas, negative W, tiny positive W, and huge positive W!
}

static double totalDashLength(const vector<float>& dash_array)
{
    double total = 0;
    for (size_t i=0; i<dash_array.size(); i++) {
        total += dash_array[i];
    }
    return total;
}

void BakeIndexedStrokedPath::forceDashingProgress(size_t initial_index, double& end_dash_t, double& arc_length_progress, double& segment_distance_left) const
{
    if (initial_index < progress.size()-1) {
        end_dash_t = progress[initial_index+1].t;
        arc_length_progress = progress[initial_index+1].cummulative_arc_length;
    } else {
        end_dash_t = 1;
        arc_length_progress = progress.back().cummulative_arc_length;
    }
    segment_distance_left = 0;
}

double biggest_repeats = 0;

void BakeIndexedStrokedPath::dashedConicCurveTo(const double2 cp[3], float w)
{
    // Original quadratic A,B,C
    const double3 A = double3(cp[0],1),
                  B = double3(w*cp[1],w),
                  C = double3(cp[2],1);

    const float2 conic[3] = {
        float2(cp[0]),
        float2(cp[1]),
        float2(cp[2]),
    };
    progress.clear();
    stroker.tessellateConicPosition(nextRibSegmentFunc, this, conic, w);

    double arc_length_progress = 0;

    const double segment_arc_length = segmentArcLength();
    double arc_length_left = segment_arc_length;
    double segment_distance_into_pattern = dashing_state.distanceIntoPattern();
    double segment_distance_left = dash_array[dashing_state.CurrentSegment()] - segment_distance_into_pattern;

    const double total_dash_length = totalDashLength(dash_array);
    assert(total_dash_length > 0);

    double start_dash_t = 0;
    double end_dash_t = 0;

    arc_length_progress = segment_distance_left;
    size_t initial_index = 0;  // Initially binary search entire progress vector.

    // While this line segment has more arc length left, keep dashing...
    while (arc_length_progress < segment_arc_length) {
        if (verbose) {
            printf("current_segment = %d, dash_on = %d\n",
                int(dashing_state.CurrentSegment()), dashing_state.DashOn());
            printf("segment_distance_into_pattern = %f, segment_distance_left = %f\n",
                segment_distance_into_pattern, segment_distance_left);
        }
        end_dash_t = tForDistanceIntoConicSegment(arc_length_progress, w, initial_index);
        if (start_dash_t > end_dash_t) {
            // Rare, but ensures progress.
            end_dash_t = start_dash_t;
        }

        if (dashing_state.DashOn() && end_dash_t > 0) {
            // Apply de Casteljau's algorithm for cubic chopping over the range [0,t1]
            const double3 ab = lerp(A, B, end_dash_t),
                          bc = lerp(B, C, end_dash_t),
                          abc = lerp(ab, bc, end_dash_t);

            assert(end_dash_t != 0);
            double amended_start_dash_t = start_dash_t/end_dash_t;

            // Apply de Casteljau's algorithm for cubic chopping over the range [0,t1]
            const double3 ab2 = lerp(A, ab, amended_start_dash_t),
                          bc2 = lerp(ab, abc, amended_start_dash_t),
                          abc2 = lerp(ab2, bc2, amended_start_dash_t);

            const double abc2_w = abc2.z;
            const double bc2_w = bc2.z;
            const double abc_w = abc.z;

            const double canonical_w64 = bc2_w/(sqrt(abc2_w)*sqrt(abc_w));
            const float canonical_w = float(canonical_w64);

            if (canonical_w >= 0) {
                const float2 split_quadratic[3] = {
                    float2(abc2.xy/abc2_w),
                    float2(bc2.xy/bc2_w),
                    float2(abc.xy/abc_w)
                };

                if (needs_terminal_dash_cap != BOGUS_INDEX) {
                    const StrokeLink &s = link[needs_terminal_dash_cap];
                    // Terminal dash cap.
                    addCap(TERMINAL_DASH_CAP, s.ndx[3], s.ndx[5]);
                }
                addConicCurveTo(split_quadratic, canonical_w);
                needs_terminal_dash_cap = link.size()-1;
                join_needed = false;
            } else {
                // Negative or undefined w means skip forward a progress step to avoid getting stuck.
                forceDashingProgress(initial_index, end_dash_t, arc_length_progress, segment_distance_left);
            }
        }

        assert(start_dash_t <= end_dash_t);
        start_dash_t = end_dash_t;

        dashing_state.advancePatternElement(dash_array);
        arc_length_left -= segment_distance_left;

        segment_distance_into_pattern = 0;
        segment_distance_left = dash_array[dashing_state.CurrentSegment()];

        // Have we encountered a rib with an insurmountable arc length gap?
        const double needed_progress = progress[initial_index].arc_length;
        const double repeats = needed_progress/total_dash_length;
        // repeat_limit says the dash pattern shouldn't repeat over 128 times
        // in a small angle change.  Without out this, we can easily fill up
        // memory with dashing segments.  This usually happens under extreme
        // projection.
        const double repeat_limit = 128;
        if (repeats < repeat_limit) {
            // No, so make progress normally.
            arc_length_progress += segment_distance_left;
            biggest_repeats = max(biggest_repeats, repeats);
        } else {
            // Yes, force progress.
            forceDashingProgress(initial_index, end_dash_t, arc_length_progress, segment_distance_left);
        }
    }

    // Finish this line segment (it ends before the current dash segement ends)
    dashing_state.advanceDistance(arc_length_left);
    if (dashing_state.DashOn()) {
        if (needs_terminal_dash_cap != BOGUS_INDEX) {
            const StrokeLink &s = link[needs_terminal_dash_cap];
            // Terminal dash cap.
            addCap(TERMINAL_DASH_CAP, s.ndx[3], s.ndx[5]);
        }
        // Apply de Casteljau's algorithm for cubic chopping over the range [0,t1]
        const double3 ab = lerp(A, B, end_dash_t),
                      bc = lerp(B, C, end_dash_t),
                      abc = lerp(ab, bc, end_dash_t);

        const double abc_w = abc.z;
        const double bc_w = bc.z;
        const double C_w = C.z;

        const float canonical_w = float(bc_w/(sqrt(abc_w)*sqrt(C_w)));

        const float2 split_quadratic[4] = {
            float2(abc.xy/abc_w),
            float2(bc.xy/bc_w),
            float2(C.xy/C_w),
        };
        const size_t size_prior_to_final_conic = link.size();
        addConicCurveTo(split_quadratic, canonical_w);
        if (link.size() > size_prior_to_final_conic) {
            needs_terminal_dash_cap = size_prior_to_final_conic;
            needs_terminal_dash_cap = BOGUS_INDEX;
            join_needed = true;
        }
    }
}

void BakeIndexedStrokedPath::conicCurveToHelper(const float2 p[3], float w)
{
    if (dashing_state.Dashing()) {
        const double2 pd[] = { double2(p[0]), double2(p[1]), double2(p[2]) };
        dashedConicCurveTo(pd, w);
    } else {
        addConicCurveTo(p, w);
        join_needed = true;
    }
}

void BakeIndexedStrokedPath::conicCurveTo(const float2 p[3], float w)
{
    if (verbose) {
        printf("ConicCurveTo\n");
        std::cout << " p[0] = " << p[0] << std::endl;
        std::cout << " p[1] = " << p[1] << " with w=" << w << std::endl;
        std::cout << " p[2] = " << p[2] << std::endl;
    }
    conicCurveToHelper(p, w);
}

void BakeIndexedStrokedPath::addCubicCurveTo(const float2 p[4])
{
    size_t before_size = coord.size();
    addInitialControlPoint(p[0]);
    StrokeLink s;
    for (int i=0; i<4; i++) {
        if (all(cp_back() == p[i])) {
            s.ndx[i] = GLuint(coord.size()-2);
        } else {
            cp_push_back(p[i]);
            assert(all(cp_back() == p[i]));
            s.ndx[i] = GLuint(coord.size()-2);
        }
    }
    if (s.ndx[0] == s.ndx[1]) {
        if (s.ndx[0] == s.ndx[2]) {
            if (s.ndx[0] == s.ndx[3]) {
                // All indices the same!
                // Reset cp vector to size before any push_backs from this cubicCurveTo attempt.
                coord.resize(before_size);
                // Bail without pushing s.
                return;
            } else {
                s.ndx[4] = s.ndx[3];
            }
        } else {
            s.ndx[4] = s.ndx[2];
        }
    } else {
        s.ndx[4] = s.ndx[1];
    }
    if (s.ndx[3] == s.ndx[2]) {
        if (s.ndx[3] == s.ndx[1]) {
            // All indices the same should have been detected already!
            assert(s.ndx[3] != s.ndx[0]);
            s.ndx[5] = s.ndx[0];
        } else {
            s.ndx[5] = s.ndx[1];
        }
        needs_cubic_stabilization = true;
    } else {
        s.ndx[5] = s.ndx[2];
    }
    addStrokeLink(s);
}

void BakeIndexedStrokedPath::nextRibSegment(const float2& p, float t)
{

    if (progress.size() == 0) {
        assert(t == 0);
        const CurveProgress first = { 0, 0, 0 };
        progress.push_back(first);
    } else {
        if (t > progress.back().t) {
            const double step_length = distance(double2(previous_p), double2(p));
            const double finite_step_length = Cg::isfinite(step_length) ? step_length : 0;
            const double cummulative_arc_length = segmentArcLength() + finite_step_length;
            const CurveProgress next = {
                t,
                step_length,
                cummulative_arc_length
            };
            progress.push_back(next);
        } else {
            // Non-increasing t, skip!
        }
    }
    previous_p = p;
}

// Static callback function for tessellateCubic/tessellateQuadratic/tessellateConic methods.
void BakeIndexedStrokedPath::nextRibSegmentFunc(int rib_index, const float2& p, float t, void* handle)
{
    // Cast handle to DurhamStrokePathProcessor* to invoke method to do real work.
    reinterpret_cast<BakeIndexedStrokedPath*>(handle)->nextRibSegment(p, t);
}

// Find a t corresponding to an arc length distance.
//
// Since the presumption is distance will be increasing on every call,
// initial_index into the progress vector is adjusted to narrow the
// binary search to ranges for distances yet to query.
double DashedSegmentState::tForDistanceIntoSegment(double distance, size_t& initial_index) const
{
    assert(progress.size() >= 2);

    const CurveProgress *base = &progress[initial_index];
    const size_t last = progress.size() - initial_index - 1;

    // Binary search.
    for (size_t ndx = last; ndx != 0; ndx >>= 1) {
        const CurveProgress *p = base + (ndx >> 1);
        assert(p < &progress[progress.size()-1]);
        const double start = p[0].cummulative_arc_length;
        const double end = p[1].cummulative_arc_length;
        if (start <= distance && distance <= end) {
            const float start_t = p[0].t;
            const float end_t = p[1].t;
            // Assume we can linearly interpolate t (because the interval from start to end is straight).
            // (The conic case below is more involved.)
            const double step_distance = distance - start;
            const double step_distance_range = end - start;
            assert(step_distance_range > 0);
            const double dist_t = step_distance / step_distance_range;
            const float diff_t = end_t - start_t;
            const double interp_t = start_t + dist_t*diff_t;
            initial_index = p - &progress[0];
            return interp_t;
        }
        if (distance > end) {
            base = p+1;
            ndx--;
        }
    }
    assert(!"should have found distance already");
    // Act like we reached the end of the parametric range.
    return 1;
}

// Version of tForDistanceIntoSegment that accounts of the perspective change in distance.
// Take the extrapolating control point's w value in order to compute the start and end
// w for the interval of CurveProgress bounding the distance.
//
// XXX This works well for values of w near 1, but assuming we can interpolate t in a
// perspective correct way is flawed when w is huge or small.
double DashedSegmentState::tForDistanceIntoConicSegment(double distance, float w, size_t& initial_index) const
{
    assert(progress.size() >= 2);

    const CurveProgress *base = &progress[initial_index];
    const size_t last = progress.size() - initial_index - 1;

    // Binary search.
    for (size_t ndx = last; ndx != 0; ndx >>= 1) {
        const CurveProgress *p = base + (ndx >> 1);
        assert(p < &progress[progress.size()-1]);
        const double start = p[0].cummulative_arc_length;
        const double end = p[1].cummulative_arc_length;
        if (start <= distance && distance <= end) {
            const float start_t = p[0].t;
            const float end_t = p[1].t;

            // Find the t value (dist_t) between start_t and end_t where the arc length
            // along the segment should be approximately distance.  Because the segment
            // is a conic (a rational curve), we cannot expect linear interpolation to
            // "just work".  Instead we need to use rational linear interpolation.
            //
            // Think of the start and end points as (Ax/Aw
            //
            // > d = ((1-t)*Aw*Ax + t*Bw*Bx)/((1-t)*Aw+t*Bw);
            //
            //                          (1 - t) Aw Ax + t Bw Bx
            //                      d = -----------------------
            //                             (1 - t) Aw + t Bw
            // 
            // > eq := solve(%,t);
            // 
            //                                 Aw (d - Ax)
            //                   eq := ---------------------------
            //                         d Aw - d Bw - Aw Ax + Bw Bx
            // 
            // > numer(eq)/collect(denom(eq),[Aw,Bw]);
            // 
            //                              Aw (d - Ax)
            //                       --------------------------
            //                       Aw (d - Ax) + (-d + Bx) Bw
            // 
            // First interpolate w along the conic at start_t and end_t...
            const double start_w = lerp(lerp(1, w, start_t), lerp(w, 1, start_t), start_t);
            const double end_w = lerp(lerp(1, w, end_t), lerp(w, 1, end_t), end_t);
            if (start_w * end_w >= 0) {
                // Everything fine.
            } else {
                // Opposite signs for start and end w values.  Skip this rib to make progress.
                initial_index = p - &progress[0];
                return end_t;
            }
            // Now compute the t (dist_t) approximately at distance using the Maple ratio
            // above where:
            //   Ax = start
            //   Aw = start_w
            //   Bx = end
            //   Bw = end_w
            //   d = distance
            const double dist_t_numer = start_w*(distance - start);
            const double dist_t_denom = start_w*(distance - start) + end_w*(end - distance);
            if (dist_t_denom == 0) {
                // Skip this rib to make progress.
                initial_index = p - &progress[0];
                return end_t;
            }
            const double dist_t = dist_t_numer/dist_t_denom;

            // Confirm that the dist_t would evaluate to distance.
            const double distance_at_dist_t = ((1-dist_t)*start_w*start + dist_t*end_w*end)/((1-dist_t)*start_w + dist_t*end_w);
            if (distance > 0) {
                assert(distance_at_dist_t/distance < 1.001);
                assert(distance_at_dist_t/distance > 0.999);
            }

            const float diff_t = end_t - start_t;
            const double interp_t = start_t + dist_t*diff_t;
            assert(interp_t >= 0);
            assert(interp_t <= 1);
            initial_index = p - &progress[0];
            return interp_t;
        }
        if (distance > end) {
            base = p+1;
            ndx--;
        }
    }
    assert(!"should have found distance already");
    // Act like we reached the end of the parametric range.
    return 1;
}

void BakeIndexedStrokedPath::dashedCubicCurveTo(const double2 cp[4])
{
    // Original cubic A,B,C,D
    const double2 A = cp[0],
                  B = cp[1],
                  C = cp[2],
                  D = cp[3];

    const float2 cubic[4] = {
        float2(cp[0]),
        float2(cp[1]),
        float2(cp[2]),
        float2(cp[3]),
    };
    progress.clear();
    stroker.tessellateCubicPosition(nextRibSegmentFunc, this, cubic);

    double arc_length_progress = 0;

    const double segment_arc_length = segmentArcLength();
    double arc_length_left = segment_arc_length;
    double segment_distance_into_pattern = dashing_state.distanceIntoPattern();
    double segment_distance_left = dash_array[dashing_state.CurrentSegment()] - segment_distance_into_pattern;

    double start_dash_t = 0;
    double end_dash_t = 0;

    arc_length_progress = segment_distance_left;
    size_t initial_index = 0;  // Initially binary search entire progress vector.

    // While this line segment has more arc length left, keep dashing...
    while (arc_length_progress < segment_arc_length) {
        if (verbose) {
            printf("current_segment = %d, dash_on = %d\n",
                int(dashing_state.CurrentSegment()), dashing_state.DashOn());
            printf("segment_distance_into_pattern = %f, segment_distance_left = %f\n",
                segment_distance_into_pattern, segment_distance_left);
        }
        end_dash_t = tForDistanceIntoSegment(arc_length_progress, initial_index);
        assert(start_dash_t <= end_dash_t);

        if (dashing_state.DashOn() && end_dash_t > 0) {
            // Apply de Casteljau's algorithm for cubic chopping over the range [0,t1]
            const double2 ab = lerp(A, B, end_dash_t),
                          bc = lerp(B, C, end_dash_t),
                          cd = lerp(C, D, end_dash_t),
                          abc = lerp(ab, bc, end_dash_t),
                          bcd = lerp(bc, cd, end_dash_t),
                          abcd = lerp(abc, bcd, end_dash_t);

            assert(end_dash_t != 0);
            const double amended_start_dash_t = start_dash_t/end_dash_t;

            // Apply de Casteljau's algorithm for cubic chopping over the range [0,t1]
            const double2 ab2 = lerp(A, ab, amended_start_dash_t),
                          bc2 = lerp(ab, abc, amended_start_dash_t),
                          cd2 = lerp(abc, abcd, amended_start_dash_t),
                          abc2 = lerp(ab2, bc2, amended_start_dash_t),
                          bcd2 = lerp(bc2, cd2, amended_start_dash_t),
                          abcd2 = lerp(abc2, bcd2, amended_start_dash_t);

            const float2 split_cubic[4] = {
                float2(abcd2),
                float2(bcd2),
                float2(cd2),
                float2(abcd)
            };

            if (needs_terminal_dash_cap != BOGUS_INDEX) {
                const StrokeLink &s = link[needs_terminal_dash_cap];
                // Terminal dash cap.
                addCap(TERMINAL_DASH_CAP, s.ndx[3], s.ndx[5]);
            }
            addCubicCurveTo(split_cubic);
            needs_terminal_dash_cap = link.size()-1;
            join_needed = false;
        }

        assert(start_dash_t <= end_dash_t);
        start_dash_t = end_dash_t;

        dashing_state.advancePatternElement(dash_array);
        arc_length_left -= segment_distance_left;

        segment_distance_into_pattern = 0;
        segment_distance_left = dash_array[dashing_state.CurrentSegment()];

        arc_length_progress += segment_distance_left;
    }

    // Finish this line segment (it ends before the current dash segement ends)
    dashing_state.advanceDistance(arc_length_left);
    if (dashing_state.DashOn()) {
        if (needs_terminal_dash_cap != BOGUS_INDEX) {
            const StrokeLink &s = link[needs_terminal_dash_cap];
            // Terminal dash cap.
            addCap(TERMINAL_DASH_CAP, s.ndx[3], s.ndx[5]);
        }
        // Apply de Casteljau's algorithm for cubic chopping over the range [0,t1]
        const double2 ab = lerp(A, B, end_dash_t),
                      bc = lerp(B, C, end_dash_t),
                      cd = lerp(C, D, end_dash_t),
                      abc = lerp(ab, bc, end_dash_t),
                      bcd = lerp(bc, cd, end_dash_t),
                      abcd = lerp(abc, bcd, end_dash_t);

        const float2 split_cubic[4] = {
            float2(abcd),
            float2(bcd),
            float2(cd),
            float2(D)
        };
        const size_t size_prior_to_final_cubic = link.size();
        addCubicCurveTo(split_cubic);
        if (link.size() > size_prior_to_final_cubic) {
            needs_terminal_dash_cap = size_prior_to_final_cubic;
            needs_terminal_dash_cap = BOGUS_INDEX;
            join_needed = true;
        }
    }
}

void BakeIndexedStrokedPath::cubicCurveToHelper(const float2 p[4])
{
    if (dashing_state.Dashing()) {
        const double2 pd[] = { double2(p[0]), double2(p[1]), double2(p[2]), double2(p[3]) };
        dashedCubicCurveTo(pd);
    } else {
        addCubicCurveTo(p);
        join_needed = true;
    }
}

void BakeIndexedStrokedPath::cubicCurveTo(const float2 p[4])
{
    if (verbose) {
        printf("CubicCurveTo\n");
        std::cout << " p[0] = " << p[0] << std::endl;
        std::cout << " p[1] = " << p[1] << std::endl;
        std::cout << " p[2] = " << p[2] << std::endl;
        std::cout << " p[3] = " << p[3] << std::endl;
    }
    cubicCurveToHelper(p);
}

void BakeIndexedStrokedPath::splineCubicCurveTo(const float2 p[4], bool more/*ignored*/)
{
    if (verbose) {
        printf("SplineCubicCurveTo\n");
        std::cout << " p[0] = " << p[0] << std::endl;
        std::cout << " p[1] = " << p[1] << std::endl;
        std::cout << " p[2] = " << p[2] << std::endl;
        std::cout << " p[3] = " << p[3] << std::endl;
    }
    if (catmull_rom_extra_points_ptr) {
        std::vector<float2>& catmull_rom_extra_points = *catmull_rom_extra_points_ptr;

        catmull_rom_extra_points.push_back(p[1]);
        catmull_rom_extra_points.push_back(p[2]);
    }
    cubicCurveToHelper(p);
}

void BakeIndexedStrokedPath::addClosingDashCaps()
{
    assert(dashing_state.Dashing());
    assert(needs_initial_cap != BOGUS_INDEX);
    const StrokeLink &contour_end = link.back();
    addCap(TERMINAL_DASH_CAP, contour_end.ndx[3], contour_end.ndx[5]);
    const StrokeLink &contour_start = link[needs_initial_cap];
    addCap(INITIAL_DASH_CAP, contour_start.ndx[0], contour_start.ndx[4]);
    needs_initial_cap = BOGUS_INDEX;
}

void BakeIndexedStrokedPath::close(const float2 p[2])
{
    if (verbose) {
        printf("ClosePath\n");
    }
    if (any(p[0] != p[1])) {
        lineToHelper(p);
    }
    // Have we added links since the last MoveTo command?
    if (moveTo_index < link.size()) {
        // Yes...
        // Is the dash pattern currently "on"?
        if (dashing_state.DashOn()) {
            // Yes.
            const GLuint start_ndx = link[moveTo_index].ndx[0];
            const GLuint end_ndx = link.back().ndx[3];
            const float2 start_cp = cp(start_ndx);
            const float2 end_cp = cp(end_ndx);
            // Are the start and terminal positions identical?
            if (all(start_cp == end_cp)) {
                // Yes, add a join to connect the start and terminal positions.
                addJoin(link[moveTo_index]);
            } else {
                // No, just add dash caps.
                addClosingDashCaps();
            }
        } else {
            // No, just add dash caps.
            addClosingDashCaps();
        }
    }
    moveTo_index = link.size();
    needs_initial_cap = BOGUS_INDEX;
    needs_initial_dash_cap = false;
}

void BakeIndexedStrokedPath::stabilizeCubics()
{
    // XXX this reversal of control points could be a future barrier to arc length computations on cubics
    // See CPUStrokeTessellatorControl.cpp's findInflectionPoints to confirm the math is dependent
    // on the control point order of the cubic.
    for (size_t i=0; i<link.size(); i++) {
        GLuint* ndx = link[i].ndx;
        // Is a cubic link?
        if (ndx[1] != 0 && ndx[2] != 0) {
            // Yes...
            // Are the last two control points identical?
            if (ndx[3] == ndx[2]) {
                // Yes...
                // Are the first two control point different?
                if (ndx[0] != ndx[1]) {
                    // Yes...
                    // Reverse indices in link so inflection points can be computed stablely.
                    link[i].swapIndices(3,0);
                    link[i].swapIndices(2,1);
                    link[i].swapIndices(4,5);
                }
            }
        }
    }
}

void BakeIndexedStrokedPath::endPath(PathPtr &p)
{
    if (verbose) {
        printf("end path\n");
    }
    if (moveTo_index != link.size()) {
        addCap(TERMINAL_CAP, link.back().ndx[3], link.back().ndx[5]);
    }
    if (needs_initial_cap != BOGUS_INDEX) {
        const StrokeLink &s = link[needs_initial_cap];
        addCap(INITIAL_CAP, s.ndx[0], s.ndx[4]);
        needs_initial_cap = BOGUS_INDEX;
    } else {
        // closed or single point contour
    }
    if (has_nontrivial_conic) {
        // Conics will have 3 coordinates (x,y,w) per interpolating control point and
        // therefore pull 3 coordinates per vertex; so push an extra zero coordinate
        // so the last (x,y) control point has extra data.
        coord.push_back(0);
    }
    // Computing the inflection points of a cubic is not particularly stable
    // when the third & fourth control points are co-located.
    if (needs_cubic_stabilization) {
        stabilizeCubics();
    }
}

// Helper callback for CenterPointArc::splitToConics.
void BakeIndexedStrokedPath::conicFunc(const float2 plist[3], float w, void *handle)
{
    BakeIndexedStrokedPath* processor = reinterpret_cast<BakeIndexedStrokedPath*>(handle);
    processor->conicCurveTo(plist, w);
}

void BakeIndexedStrokedPath::arcTo(const CenterPointArc &arc)
{
    if (arc.Form() == CenterPointArc::BEHAVED) {
        // XXX dashing?
        CenterPointArc::ConicSplitFunc func = conicFunc;
        arc.splitToConics(func, radians(91.0f), this);
    } else if (arc.Form() == CenterPointArc::DEGENERATE_LINE) {
        lineTo(arc.EndPoints());
    } else {
        assert(arc.Form() == CenterPointArc::DEGENERATE_POINT);
        // Do nothing.
    }
}

void BakeIndexedStrokedPath::setVerbose(bool v)
{
    verbose = v;
}

void BakeIndexedStrokedPath::setNativeQuadratics(bool v)
{
    native_quadratics = v;
}

void BakeIndexedStrokedPath::setCatmullRomExtraPointsVector(std::vector<float2>* v)
{
    catmull_rom_extra_points_ptr = v;
}

DashedSegmentState::DashedSegmentState(const DashStyle& style)
    : dash_array(style.dash_array)
    , dash_offset_reset(style.dash_offset_reset)
    , stroker(StrokeTessellatorSettings(radians(0.5f)))  // half degree in radians
    , initial_dashing_state(style.dash_array, style.dash_offset)
    , dashing_state(initial_dashing_state)
    , needs_terminal_dash_cap(BOGUS_INDEX)
    , needs_initial_dash_cap(false)
{}

BakeIndexedStrokedPath::BakeIndexedStrokedPath(const DashStyle& style)
    : DashedSegmentState(style)
    , include_caps(true)
    , include_joins(true)
    , verbose(false)
    , native_quadratics(false)
    , moveTo_index(0)
    , needs_initial_cap(BOGUS_INDEX)
    , has_nontrivial_conic(false)
    , needs_cubic_stabilization(false)
    , join_needed(false)
{}

BakeIndexedStrokedPath::BakeIndexedStrokedPath(const DashStyle& style, bool caps, bool joins)
    : DashedSegmentState(style)
    , include_caps(caps)
    , include_joins(joins)
    , verbose(false)
    , native_quadratics(false)
    , catmull_rom_extra_points_ptr(NULL)
    , moveTo_index(0)
    , needs_initial_cap(BOGUS_INDEX)
    , has_nontrivial_conic(false)
    , needs_cubic_stabilization(false)
    , join_needed(false)
{}
