Skip to content

Commit

Permalink
fix truncated join cutoff + some cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ffreyer committed Jan 18, 2024
1 parent e5af18f commit 6494010
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 72 deletions.
8 changes: 5 additions & 3 deletions GLMakie/assets/shader/lines.frag
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ struct Nothing{ //Nothing type, to encode if some variable doesn't contain any d

in vec4 f_color;
in vec4 f_quad_sdf;
in vec2 f_joint_cutoff;
// in float f_line_width;
// in float f_cumulative_length;
in vec4 f_joint_cutoff;
in vec2 f_uv;
flat in vec4 f_pattern_overwrite;
flat in uvec2 f_id;
Expand Down Expand Up @@ -92,6 +90,10 @@ void main(){
// sdf = max(sdf, abs(f_quad_sdf.z) - f_line_width);
sdf = max(sdf, max(f_quad_sdf.z, f_quad_sdf.w));

// smoothly cut off corners of truncated miter joints
sdf = max(sdf, max(f_joint_cutoff.z, f_joint_cutoff.w));

// add pattern
sdf = max(sdf, get_pattern_sdf(pattern));

// draw
Expand Down
104 changes: 36 additions & 68 deletions GLMakie/assets/shader/lines.geom
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ in float g_thickness[];

out vec4 f_color;
out vec4 f_quad_sdf; // smooth edges (along length and width)
out vec2 f_joint_cutoff; // hard edges (joint)
// out float f_line_width;
// out float f_cumulative_length;
out vec4 f_joint_cutoff; // xy = hard cutoff, zw = smooth cutoff
out vec2 f_uv;
flat out vec4 f_pattern_overwrite;
flat out uvec2 f_id;
Expand Down Expand Up @@ -58,7 +56,7 @@ How it works:
struct LineData {
vec3 p1, p2, v;
float segment_length, extrusion_a, extrusion_b;
vec2 n, miter_v_a, miter_n_a, miter_v_b, miter_n_b;
vec2 n, n0, n2, miter_v_a, miter_n_a, miter_v_b, miter_n_b;
float miter_offset_a, miter_offset_b;
bool is_start, is_end;
};
Expand Down Expand Up @@ -105,73 +103,37 @@ void process_pattern(sampler1D pattern, LineData line) {
}

void emit_vertex(vec3 origin, vec2 center, LineData line, int index, vec2 geom_offset) {

vec3 position = origin + geom_offset.x * line.v + vec3(geom_offset.y * line.n, 0);

// sdf prototyping
// sdf generation

bool is_a_truncated_joint = line.miter_offset_a < 0.5;
bool is_b_truncated_joint = line.miter_offset_b < 0.5;

// joint hard cutoff
// TODO Problem: this can cut the whole line even if the next segment does
// fill that space (due to being short for example)
// Options:
// - don't do this for truncated join -> overlap, need to clean extra corner generated by extrusion
// if line start/end move hard cutoff out so smooth rect cutoff can act
vec2 VP1 = position.xy - line.p1.xy;
vec2 VP2 = position.xy - line.p2.xy;
f_joint_cutoff = vec2(
line.is_start ? -10.0 : dot(VP1, -line.miter_v_a),
line.is_end ? -10.0 : dot(VP2, line.miter_v_b)
);
// note miter_v_a and miter_v_b are different, so no -width .. + width merge
// also no max


// rect
// vec2 VC = position.xy - center;
// can't have absolute, will break interpolation
// f_rect_sdf = vec2(
// abs(dot(VC, v)) - 0.5 * segment_length, // - extrusion, apply in frag? do we need 0 edge?
// abs(dot(VC, n)) - thickness
// );
// f_rect_sdf = vec2(dot(VC, line.v.xy), dot(VC, line.n));

// joint soft cutoff
// need miter_offset to be big if no truncation
// f_joint_smooth = vec2(100.0, 100.0);
// if (line.miter_offset_a < 0.5) // truncated join condition
// f_joint_smooth.x = dot(VP1, line.miter_n_a) - 0.5 * g_thickness[1] * line.miter_offset_a;
// if (line.miter_offset_b < 0.5) // truncated join condition
// f_joint_smooth.y = dot(VP2, line.miter_n_b) - 0.5 * g_thickness[2] * line.miter_offset_b;


/*
#######
width sdf:
vert/geom: x = dot(vertex_pos - center, line.n)
frag: sd = abs(x) - unpadded_linewidth
length sdf:
start sdf:
vert/geom: x = dot(vertex_pos - P1, -line.v)
frag: sd = x
stop sdf:
vert/goem: x = dot(vertex_pos - P2, line.v);
frag: sd = x
smooth truncated join sdf:
vert/geom: x = dot(vertex_pos - P1/P2, sign(dot(miter_v, v)) * miter_v) - miter_distance
frag: sd = x
sharp join sdf:
vert/geom: x = -10.0
frag: sd = x
*/

// SDF in (v dir at P1, v dir at P2, n dir) (default is sharp joint)
// f_quad_sdf = vec3(-1.0, -1.0, dot(position.xy - center, line.n));
float temp = dot(position.xy - center, line.n);
// f_quad_sdf = vec4(-1.0, -1.0, temp - 0.5 * g_thickness[index], 0.5 * g_thickness[index] - temp);
f_quad_sdf = vec4(-1.0, -1.0, temp - 0.5 * g_thickness[index], -temp - 0.5 * g_thickness[index]);

bool is_a_truncated_joint = line.miter_offset_a < 0.5;
bool is_b_truncated_joint = line.miter_offset_b < 0.5;
// by default joint cutoffs do nothing
f_joint_cutoff = vec4(-10.0);

// sharp joints use sharp (pixelated) cut offs to avoid self-overlap
if (!line.is_start && !is_a_truncated_joint)
f_joint_cutoff.x = dot(VP1, -line.miter_v_a);
if (!line.is_end && !is_b_truncated_joint)
f_joint_cutoff.y = dot(VP2, line.miter_v_b);

// truncated joints use smooth cutoff for corners that are outside the other segment
// TODO: this slightly degrades AA quality due to two AA edges overlapping
if (is_a_truncated_joint)
f_joint_cutoff.z = dot(VP1, -sign(dot(line.v.xy, line.n0)) * line.n0) - 0.5 * g_thickness[1];
if (is_b_truncated_joint)
f_joint_cutoff.w = dot(VP2, sign(dot(line.v.xy, line.n2)) * line.n2) - 0.5 * g_thickness[2];


// SDF of quad

// In line direction
if (line.is_start) // flat line end
f_quad_sdf.x = dot(VP1, -line.v.xy);
else if (is_a_truncated_joint)
Expand All @@ -182,14 +144,17 @@ void emit_vertex(vec3 origin, vec2 center, LineData line, int index, vec2 geom_o
else if (is_b_truncated_joint)
f_quad_sdf.y = dot(VP2, line.miter_n_b) - 0.5 * g_thickness[2] * line.miter_offset_b;

// if truncated or no join -> use lw at p
// else -> use lw at p + dot(miter_n, v) * sign(miter_n, offset dir)
// float line_length = line.segment_length;

// In line normal direction (linewidth)
// This mostly works
// TODO: integrate better
// TODO Problem: you can get concave shapes with very different linewidths + zooming
// we want to adjust v direction on respective side to dodge both edge normals...

// start/end/truncated joint: use g_thickness[i] at point i
// sharp joint: use g_thickness[i] at point i +- joint offset to avoid different
// linewidth between this and the previous/next segment
// TODO: can we calculate this more efficiently?
// top
float offset_a = !is_a_truncated_joint && !line.is_start ? line.extrusion_a : 0.0;
float offset_b = !is_b_truncated_joint && !line.is_end ? line.extrusion_b : 0.0;
Expand All @@ -207,10 +172,11 @@ void emit_vertex(vec3 origin, vec2 center, LineData line, int index, vec2 geom_o
edge_normal = vec2(-edge_vector.y, edge_vector.x);
f_quad_sdf.w = dot(position.xy - corner1, -edge_normal);

// And the simpler things...
f_color = g_color[index];
gl_Position = vec4((2.0 * position.xy / resolution) - 1.0, position.z, 1.0);
f_id = g_id[index];
// TODO: pattern sampling
// index into pattern
f_uv = vec2(
(g_lastlen[index] + geom_offset.x) / pattern_length,
0.5 + geom_offset.y / g_thickness[index]
Expand Down Expand Up @@ -304,6 +270,8 @@ void main(void)
line.p2 = p2;
line.v = v1;
line.n = n1;
line.n0 = n0;
line.n2 = n2;
line.segment_length = segment_length;
line.is_start = !isvalid[0];
line.is_end = !isvalid[3];
Expand Down
2 changes: 1 addition & 1 deletion GLMakie/src/drawing_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(plot::Lines))
linewidth = gl_attributes[:thickness]
px_per_unit = data[:px_per_unit]
data[:pattern] = map(linestyle, linewidth, px_per_unit) do ls, lw, ppu
ppu * _mean(lw) .* ls
return ppu * _mean(lw) .* ls
end
data[:fast] = false

Expand Down

0 comments on commit 6494010

Please sign in to comment.