@bokeh/bokehjs
Version:
Interactive, novel data visualization
238 lines (203 loc) • 8.72 kB
JavaScript
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;
uniform sampler2D u_dash_tex;
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;
varying float v_length_so_far;
varying vec4 v_dash_tex_info;
varying float v_dash_scale;
varying float v_dash_offset;
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);
}
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);
}
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));
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);
}
alpha = v_line_color.a*alpha;
gl_FragColor = vec4(v_line_color.rgb*alpha, alpha); // Premultiplied alpha.
}
`;
export default shader;