@bokeh/bokehjs
Version:
Interactive, novel data visualization
231 lines (200 loc) • 7.86 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;
attribute vec2 a_position;
attribute vec2 a_point_prev;
attribute vec2 a_point_start;
attribute vec2 a_point_end;
attribute vec2 a_point_next;
attribute float a_show_prev;
attribute float a_show_curr;
attribute float a_show_next;
attribute float a_linewidth;
attribute vec4 a_line_color;
attribute float a_line_cap;
attribute float a_line_join;
attribute float a_length_so_far;
attribute vec4 a_dash_tex_info;
attribute float a_dash_scale;
attribute float a_dash_offset;
uniform vec2 u_canvas_size;
uniform float u_antialias;
uniform float u_miter_limit;
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; // Boolean 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);
}
// Calculate cos/sin turn angle with adjacent segment, and unit normal vector to right
float calc_turn_angle(in bool has_cap, in vec2 segment_right, in vec2 other_right, out vec2 point_right, out float sin_turn_angle)
{
float cos_turn_angle;
vec2 diff = segment_right + other_right;
float len = length(diff);
if (has_cap || len < SMALL) {
point_right = segment_right;
cos_turn_angle = -1.0; // Turns back on itself.
sin_turn_angle = 0.0;
}
else {
point_right = diff / len;
cos_turn_angle = dot(segment_right, other_right); // cos zero at +/-pi/2, +ve angle is turn right
sin_turn_angle = cross_z(segment_right, other_right);
}
return cos_turn_angle;
}
// If miter too large use bevel join instead
bool miter_too_large(in int join_type, in float cos_turn_angle)
{
float cos_half_angle_sqr = 0.5*(1.0 + cos_turn_angle); // Trig identity
return join_type == miter_join && cos_half_angle_sqr < 1.0 / (u_miter_limit*u_miter_limit);
}
vec2 normalize_check_len(in vec2 vec, in float len)
{
if (abs(len) < SMALL)
return vec2(1.0, 0.0);
else
return vec / len;
}
vec2 normalize_check(in vec2 vec)
{
return normalize_check_len(vec, length(vec));
}
void main()
{
if (a_show_curr < 0.5) {
// Line segment has non-finite value at one or both ends, do not render.
gl_Position = vec4(-2.0, -2.0, 0.0, 1.0);
return;
}
int join_type = int(a_line_join + 0.5);
int cap_type = int(a_line_cap + 0.5);
v_linewidth = a_linewidth;
v_line_color = a_line_color;
if (v_linewidth < 1.0) {
// Linewidth less than 1 is implemented as 1 but with reduced alpha.
v_line_color.a *= v_linewidth;
v_linewidth = 1.0;
}
float halfwidth = 0.5*(v_linewidth + u_antialias);
vec2 segment_along = a_point_end - a_point_start;
v_segment_length = length(a_point_end - a_point_start);
segment_along = normalize_check_len(segment_along, v_segment_length); // unit vector.
vec2 segment_right = right_vector(segment_along); // unit vector.
vec2 xy;
// in screen coords
vec2 prev_along = normalize_check(a_point_start - a_point_prev);
vec2 prev_right = right_vector(prev_along);
vec2 next_right = right_vector(normalize_check(a_point_next - a_point_end));
v_coords.y = a_position.y*halfwidth; // Overwritten later for join points.
// Start and end cap properties
bool has_start_cap = a_show_prev < 0.5;
bool has_end_cap = a_show_next < 0.5;
// Start and end join properties
vec2 point_right_start, point_right_end;
float sin_turn_angle_start, sin_turn_angle_end;
v_cos_turn_angle_start = calc_turn_angle(has_start_cap, segment_right, prev_right, point_right_start, sin_turn_angle_start);
v_cos_turn_angle_end = calc_turn_angle(has_end_cap, segment_right, next_right, point_right_end, sin_turn_angle_end);
float sign_turn_right_start = sin_turn_angle_start >= 0.0 ? 1.0 : -1.0;
bool miter_too_large_start = !has_start_cap && miter_too_large(join_type, v_cos_turn_angle_start);
bool miter_too_large_end = !has_end_cap && miter_too_large(join_type, v_cos_turn_angle_end);
float sign_at_start = -sign(a_position.x); // +ve at segment start, -ve end.
vec2 point = sign_at_start > 0.0 ? a_point_start : a_point_end;
if ( (has_start_cap && sign_at_start > 0.0) ||
(has_end_cap && sign_at_start < 0.0) ) {
// Cap.
xy = point - segment_right*(halfwidth*a_position.y);
if (cap_type == butt_cap)
xy -= sign_at_start*0.5*u_antialias*segment_along;
else
xy -= sign_at_start*halfwidth*segment_along;
}
else if (sign_at_start > 0.0) {
vec2 inside_point = a_point_start + segment_right*(sign_turn_right_start*halfwidth);
vec2 prev_outside_point = a_point_start - prev_right*(sign_turn_right_start*halfwidth);
// join at start.
if (join_type == round_join || join_type == bevel_join || miter_too_large_start) {
if (v_cos_turn_angle_start <= 0.0) { // |turn_angle| > 90 degrees
xy = a_point_start - segment_right*(halfwidth*a_position.y) - halfwidth*segment_along;
}
else {
if (a_position.x < -1.5) {
xy = prev_outside_point;
v_coords.y = -dot(xy - a_point_start, segment_right);
}
else if (a_position.y*sign_turn_right_start > 0.0) { // outside corner of turn
float d = halfwidth*abs(sin_turn_angle_start);
xy = a_point_start - segment_right*(halfwidth*a_position.y) - d*segment_along;
}
else { // inside corner of turn
xy = inside_point;
}
}
}
else { // miter join
if (a_position.x < -1.5) {
xy = prev_outside_point;
v_coords.y = -dot(xy - a_point_start, segment_right);
}
else if (a_position.y*sign_turn_right_start > 0.0) { // outside corner of turn
float tan_half_turn_angle = (1.0-v_cos_turn_angle_start) / sin_turn_angle_start; // Trig identity
float d = sign_turn_right_start*halfwidth*tan_half_turn_angle;
xy = a_point_start - segment_right*(halfwidth*a_position.y) - d*segment_along;
}
else { // inside corner if turn
xy = inside_point;
}
}
}
else {
xy = point - segment_right*(halfwidth*a_position.y);
}
vec2 pos = xy + 0.5; // Bokeh's offset.
pos /= u_canvas_size; // in 0..1
gl_Position = vec4(2.0*pos.x - 1.0, 1.0 - 2.0*pos.y, 0.0, 1.0);
bool turn_right_start = sin_turn_angle_start >= 0.0;
bool turn_right_end = sin_turn_angle_end >= 0.0;
v_coords.x = dot(xy - a_point_start, segment_along);
v_flags = float(int(has_start_cap) +
2*int(has_end_cap) +
4*int(miter_too_large_start) +
8*int(miter_too_large_end) +
16*int(turn_right_start) +
32*int(turn_right_end));
v_line_cap = a_line_cap;
v_line_join = a_line_join;
v_length_so_far = a_length_so_far;
v_dash_tex_info = a_dash_tex_info;
v_dash_scale = a_dash_scale;
v_dash_offset = a_dash_offset;
}
`;
export default shader;