const shader = `
precision mediump float;

const int butt_cap   = 0;
const int round_cap  = 1;
const int square_cap = 2;

const int miter_join = 0;
const int round_join = 1;
const int bevel_join = 2;

uniform float u_antialias;
#ifdef DASHED
uniform sampler2D u_dash_tex;
#endif

varying float v_linewidth;
varying vec4 v_line_color;
varying float v_line_cap;
varying float v_line_join;
varying float v_segment_length;
varying vec2 v_coords;
varying float v_flags;
varying float v_cos_turn_angle_start;
varying float v_cos_turn_angle_end;
#ifdef DASHED
varying float v_length_so_far;
varying vec4 v_dash_tex_info;
varying float v_dash_scale;
varying float v_dash_offset;
#endif

#define ONE_MINUS_SMALL (1.0 - 1e-6)

float cross_z(in vec2 v0, in vec2 v1)
{
    return v0.x*v1.y - v0.y*v1.x;
}

vec2 right_vector(in vec2 v)
{
    return vec2(v.y, -v.x);
}

float bevel_join_distance(in vec2 coords, in vec2 other_right, in float sign_turn_right)
{
    // other_right is unit vector facing right of the other (previous or next) segment, in coord reference frame
    float hw = 0.5*v_linewidth;  // Not including antialiasing
    if (other_right.y >= ONE_MINUS_SMALL) {  // other_right.y is -cos(turn_angle)
        // 180 degree turn.
        return abs(hw - v_coords.x);
    }
    else {
        const vec2 segment_right = vec2(0.0, -1.0);
        // corner_right is unit vector bisecting corner facing right, in coord reference frame
        vec2 corner_right = normalize(other_right + segment_right);
        vec2 outside_point = (-hw*sign_turn_right)*segment_right;
        return hw + sign_turn_right*dot(outside_point - coords, corner_right);
    }
}

float cap(in int cap_type, in float x, in float y)
{
    // x is distance along segment in direction away from end of segment,
    // y is distance across segment.
    if (cap_type == butt_cap)
        return max(0.5*v_linewidth - x, abs(y));
    else if (cap_type == square_cap)
        return max(-x, abs(y));
    else  // cap_type == round_cap
        return distance(vec2(min(x, 0.0), y), vec2(0.0, 0.0));
}

float distance_to_alpha(in float dist)
{
    return 1.0 - smoothstep(0.5*(v_linewidth - u_antialias),
                            0.5*(v_linewidth + u_antialias), dist);
}

vec2 turn_angle_to_right_vector(in float cos_turn_angle, in float sign_turn_right)
{
    float sin_turn_angle = sign_turn_right*sqrt(1.0 - cos_turn_angle*cos_turn_angle);
    return vec2(sin_turn_angle, -cos_turn_angle);
}

#ifdef DASHED
float dash_distance(in float x)
{
    // x is in direction of v_coords.x, i.e. along segment.
    float tex_length = v_dash_tex_info.x;
    float tex_offset = v_dash_tex_info.y;
    float tex_dist_min = v_dash_tex_info.z;
    float tex_dist_max = v_dash_tex_info.w;

    // Apply offset.
    x += v_length_so_far - v_dash_scale*tex_offset + v_dash_offset;

    // Interpolate within texture to obtain distance to dash.
    float dist = texture2D(u_dash_tex,
                           vec2(x / (tex_length*v_dash_scale), 0.0)).a;

    // Scale distance within min and max limits.
    dist = tex_dist_min + dist*(tex_dist_max - tex_dist_min);

    return v_dash_scale*dist;
}

mat2 rotation_matrix(in vec2 other_right)
{
    float sin_angle = other_right.x;
    float cos_angle = -other_right.y;
    return mat2(cos_angle, -sin_angle, sin_angle, cos_angle);
}
#endif

void main()
{
    int join_type = int(v_line_join + 0.5);
    int cap_type = int(v_line_cap + 0.5);
    float halfwidth = 0.5*(v_linewidth + u_antialias);
    float half_antialias = 0.5*u_antialias;

    // Extract flags.
    int flags = int(v_flags + 0.5);
    bool turn_right_end = (flags / 32 > 0);
    float sign_turn_right_end = turn_right_end ? 1.0 : -1.0;
    flags -= 32*int(turn_right_end);
    bool turn_right_start = (flags / 16 > 0);
    float sign_turn_right_start = turn_right_start ? 1.0 : -1.0;
    flags -= 16*int(turn_right_start);
    bool miter_too_large_end = (flags / 8 > 0);
    flags -= 8*int(miter_too_large_end);
    bool miter_too_large_start = (flags / 4 > 0);
    flags -= 4*int(miter_too_large_start);
    bool has_end_cap = (flags / 2 > 0);
    flags -= 2*int(has_end_cap);
    bool has_start_cap = flags > 0;

    // Unit vectors to right of previous and next segments in coord reference frame
    vec2 prev_right = turn_angle_to_right_vector(v_cos_turn_angle_start, sign_turn_right_start);
    vec2 next_right = turn_angle_to_right_vector(v_cos_turn_angle_end, sign_turn_right_end);

    float dist = v_coords.y;  // For straight segment, and miter join.

    // Along-segment coords with respect to end of segment, facing inwards
    vec2 end_coords = vec2(v_segment_length, 0.0) - v_coords;

    if (v_coords.x <= half_antialias) {
        // At start of segment, either cap or join.
        if (has_start_cap)
            dist = cap(cap_type, v_coords.x, v_coords.y);
        else if (join_type == round_join) {
            if (v_coords.x <= 0.0)
                dist = distance(v_coords, vec2(0.0, 0.0));
        }
        else {  // bevel or miter join
            if (join_type == bevel_join || miter_too_large_start)
                dist = max(abs(dist), bevel_join_distance(v_coords, prev_right, sign_turn_right_start));
            float prev_sideways_dist = -sign_turn_right_start*dot(v_coords, prev_right);
            dist = max(abs(dist), prev_sideways_dist);
        }
    }

    if (end_coords.x <= half_antialias) {
        if (has_end_cap) {
            dist = max(abs(dist), cap(cap_type, end_coords.x, v_coords.y));
        }
        else if (join_type == bevel_join || miter_too_large_end) {
            // Bevel join at end impacts half antialias distance
            dist = max(abs(dist), bevel_join_distance(end_coords, next_right, sign_turn_right_end));
        }
    }

    float alpha = distance_to_alpha(abs(dist));

#ifdef DASHED
    if (v_dash_tex_info.x >= 0.0) {
        // Dashes in straight segments (outside of joins) are easily calculated.
        dist = dash_distance(v_coords.x);

        vec2 prev_coords = rotation_matrix(prev_right)*v_coords;
        float start_dash_distance = dash_distance(0.0);

        if (!has_start_cap && cap_type == butt_cap) {
            // Outer of start join rendered solid color or not at all depending on whether corner
            // point is in dash or gap, with antialiased ends.
            bool outer_solid = start_dash_distance >= 0.0 && v_coords.x < half_antialias && prev_coords.x > -half_antialias;
            if (outer_solid) {
                // Within solid outer region, antialiased at ends
                float half_aa_dist = dash_distance(half_antialias);
                if (half_aa_dist > 0.0)  // Next dash near, do not want antialiased gap
                    dist = half_aa_dist - v_coords.x + half_antialias;
                else
                    dist = start_dash_distance - v_coords.x;

                half_aa_dist = dash_distance(-half_antialias);
                if (half_aa_dist > 0.0)  // Prev dash nearm do not want antialiased gap
                    dist = min(dist, half_aa_dist + prev_coords.x + half_antialias);
                else
                    dist = min(dist, start_dash_distance + prev_coords.x);
            }
            else {
                // Outer not rendered, antialias ends.
                if (v_coords.x < half_antialias)
                    dist = min(0.0, dash_distance(half_antialias) - half_antialias) + v_coords.x;

                if (prev_coords.x > -half_antialias && prev_coords.x <= half_antialias) {
                    // Antialias from end of previous segment into join
                    float prev_dist = min(0.0, dash_distance(-half_antialias) - half_antialias) - prev_coords.x;
                    // Consider width of previous segment
                    prev_dist = min(prev_dist, 0.5*v_linewidth - abs(prev_coords.y));
                    dist = max(dist, prev_dist);
                }
            }
        }

        if (!has_end_cap && cap_type == butt_cap && end_coords.x < half_antialias) {
            float end_dash_distance = dash_distance(v_segment_length);
            bool increasing = end_dash_distance >= 0.0 && sign_turn_right_end*v_coords.y < 0.0;
            if (!increasing) {
                float half_aa_dist = dash_distance(v_segment_length - half_antialias);
                dist = min(0.0, half_aa_dist - half_antialias) + end_coords.x;
            }
        }

        dist = cap(cap_type, dist, v_coords.y);

        float dash_alpha = distance_to_alpha(dist);
        alpha = min(alpha, dash_alpha);
    }
#endif

    alpha = v_line_color.a*alpha;
    gl_FragColor = vec4(v_line_color.rgb*alpha, alpha);  // Premultiplied alpha.
}
`;
export default shader;
