From ba3b20df63bc238b1c96a5a20f840d19fbacc017 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 1 Mar 2024 13:29:09 +0100 Subject: [PATCH 01/37] add attributes --- MakieCore/src/basic_plots.jl | 4 ++++ src/conversions.jl | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index c693e73c23b..d173852cb1f 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -309,6 +309,10 @@ Creates a connected line plot for each element in `(x, y, z)`, `(x, y)` or `posi linewidth = @inherit linewidth "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" linestyle = nothing + "Sets the type of linecap used, i.e. :butt (flat with no extrusion), :square (flat with 1 linewidth extrusion) or :round." + capstyle = :butt, # TODO doc: butt square round + "Controls whether line joints are rounded (:round) or not (:auto)." + jointstyle = :auto, # TODO doc: auto (miter, bevel) round "Sets which attributes to cycle when creating multiple plots." cycle = [:color] mixin_generic_plot_attributes()... diff --git a/src/conversions.jl b/src/conversions.jl index 6e196413942..789597faa4a 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -1072,6 +1072,23 @@ function convert_gaps(gaps::GapType) return (dot_gap = dot_gap, dash_gap = dash_gap) end +function convert_attribute(value::Symbol, k::key"capstyle") + # TODO: make this an enum? + vals = Dict(:butt => 0, :square => 1, :round => 2) + return get(vals, value) do + error("$value is not a valid cap style. It must be one of $(keys(vals)).") + end +end +function convert_attribute(value::Symbol, k::key"capstyle") + # TODO: make this an enum? + vals = Dict(:auto => 0, :round => 1) + return get(vals, value) do + error("$value is not a valid joint style. It must be one of $(keys(vals)).") + end +end + + + convert_attribute(c::Tuple{<: Number, <: Number}, ::key"position") = Point2f(c[1], c[2]) convert_attribute(c::Tuple{<: Number, <: Number, <: Number}, ::key"position") = Point3f(c) convert_attribute(c::VecTypes{N}, ::key"position") where N = Point{N, Float32}(c) From 8818c78e1c5be985eda8817c2aac785466f43f23 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 4 Apr 2024 18:24:59 +0200 Subject: [PATCH 02/37] prototype linecap & linestyle in GLMakie --- GLMakie/assets/shader/line_segment.geom | 2 ++ GLMakie/assets/shader/lines.frag | 30 +++++++++++++++++++++---- GLMakie/assets/shader/lines.geom | 16 ++++++++++--- MakieCore/src/basic_plots.jl | 6 ++--- src/conversions.jl | 6 ++--- 5 files changed, 47 insertions(+), 13 deletions(-) diff --git a/GLMakie/assets/shader/line_segment.geom b/GLMakie/assets/shader/line_segment.geom index fd0dab16f1f..270c4dc8720 100644 --- a/GLMakie/assets/shader/line_segment.geom +++ b/GLMakie/assets/shader/line_segment.geom @@ -32,6 +32,7 @@ flat out {{stripped_color_type}} f_color1; flat out {{stripped_color_type}} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; +flat out ivec2 f_capmode; const float AA_RADIUS = 0.8; const float AA_THICKNESS = 2.0 * AA_RADIUS; @@ -93,6 +94,7 @@ void main(void) f_linestart = 0; // no corners so no joint extrusion to consider f_linelength = segment_length; // and also no changes in line length f_cumulative_length = 0.0; // resets for each new segment + f_capmode = ivec2(0,0); // TODO // Generate vertices diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index 5e6515fa53a..750ab6d3698 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -18,7 +18,7 @@ in vec2 f_truncation; in float f_linestart; in float f_linelength; -flat in float f_linewidth; +flat in float f_linewidth; // half the real linewidth here because we count from center flat in vec4 f_pattern_overwrite; flat in vec2 f_extrusion; flat in vec2 f_discard_limit; @@ -27,6 +27,7 @@ flat in {{stripped_color_type}} f_color2; flat in float f_alpha_weight; flat in uvec2 f_id; flat in float f_cumulative_length; +flat in ivec2 f_capmode; {{pattern_type}} pattern; uniform float pattern_length; @@ -149,14 +150,35 @@ if (!debug) { // SDF for inside vs outside along the line direction. extrusion adjusts // the distance from p1/p2 for joints etc - float sdf = max(f_quad_sdf1.x - f_extrusion.x, f_quad_sdf1.y - f_extrusion.y); + float sdf; + + if (f_capmode.x == 2) { // rounded joint or cap + sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); + } else if (f_capmode.x == 1) { // :square cap + sdf = f_quad_sdf1.x - f_linewidth; + } else // default miter joint / :butt cap + sdf = f_quad_sdf1.x - f_extrusion.x; + + if (f_capmode.y == 2) { // rounded joint or cap + sdf = max(sdf, + min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) + ); + } else if (f_capmode.y == 1) { // :square cap + sdf = max(sdf, f_quad_sdf1.y - f_linewidth); + } else // default miter joint / :butt cap + sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); + + + // distance in linewidth direction sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); // outer truncation of truncated joints (smooth outside edge) - sdf = max(sdf, f_truncation.x); - sdf = max(sdf, f_truncation.y); + if (f_capmode.x != 2) + sdf = max(sdf, f_truncation.x); + if (f_capmode.y != 2) + sdf = max(sdf, f_truncation.y); // inner truncation (AA for overlapping parts) // min(a, b) keeps what is inside a and b diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 38774f88047..7496fc60023 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -33,6 +33,7 @@ flat out {{stripped_color_type}} f_color1; flat out {{stripped_color_type}} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; +flat out ivec2 f_capmode; out vec3 o_view_pos; out vec3 o_view_normal; @@ -41,6 +42,9 @@ out vec3 o_view_normal; uniform float pattern_length; uniform vec2 resolution; +uniform int capstyle; +uniform int jointstyle; + // Constants const float MITER_LIMIT = -0.4; const float AA_RADIUS = 0.8; @@ -372,8 +376,8 @@ void main(void) // if joint skipped elongate to new length // if normal joint elongate a lot to let discard/truncation handle joint f_extrusion = vec2( - !isvalid[0] ? min(AA_RADIUS, halfwidth) : (adjustment[0] == 0.0 ? 1e12 : halfwidth * abs(extrusion[0][0])), - !isvalid[3] ? min(AA_RADIUS, halfwidth) : (adjustment[1] == 0.0 ? 1e12 : halfwidth * abs(extrusion[1][0])) + !isvalid[0] ? 0.0 : (adjustment[0] == 0.0 ? 1e12 : halfwidth * abs(extrusion[0][0])), + !isvalid[3] ? 0.0 : (adjustment[1] == 0.0 ? 1e12 : halfwidth * abs(extrusion[1][0])) ); // used to compute width sdf @@ -389,6 +393,12 @@ void main(void) // for uv's f_cumulative_length = g_lastlen[1]; + // 0 :butt/normal cap or joint | 1 :square cap | 2 rounded cap/joint + f_capmode = ivec2( + isvalid[0] ? jointstyle : capstyle, + isvalid[3] ? jointstyle : capstyle + ); + // Generate interpolated/varying outputs: LineVertex vertex; @@ -403,7 +413,7 @@ void main(void) if (is_truncated[x] || !isvalid[3*x]) { // handle overlap in fragment shader via SDF comparison offset = shape_factor[y] * ( - (halfwidth * extrusion[x][y] + (2 * x - 1) * AA_THICKNESS) * v1 + + (halfwidth * max(1.0, abs(extrusion[x][y])) + AA_THICKNESS) * (2 * x - 1) * v1 + vec3((2 * y - 1) * (halfwidth + AA_THICKNESS) * n1, 0) ); } else { diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index d173852cb1f..49210d66d83 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -310,9 +310,9 @@ Creates a connected line plot for each element in `(x, y, z)`, `(x, y)` or `posi "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" linestyle = nothing "Sets the type of linecap used, i.e. :butt (flat with no extrusion), :square (flat with 1 linewidth extrusion) or :round." - capstyle = :butt, # TODO doc: butt square round - "Controls whether line joints are rounded (:round) or not (:auto)." - jointstyle = :auto, # TODO doc: auto (miter, bevel) round + capstyle = :butt + "Controls whether line joints are rounded (:round) or not (:miter)." + jointstyle = :miter "Sets which attributes to cycle when creating multiple plots." cycle = [:color] mixin_generic_plot_attributes()... diff --git a/src/conversions.jl b/src/conversions.jl index 789597faa4a..e804abcf729 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -1072,16 +1072,16 @@ function convert_gaps(gaps::GapType) return (dot_gap = dot_gap, dash_gap = dash_gap) end -function convert_attribute(value::Symbol, k::key"capstyle") +function convert_attribute(value::Symbol, ::key"capstyle") # TODO: make this an enum? vals = Dict(:butt => 0, :square => 1, :round => 2) return get(vals, value) do error("$value is not a valid cap style. It must be one of $(keys(vals)).") end end -function convert_attribute(value::Symbol, k::key"capstyle") +function convert_attribute(value::Symbol, ::key"jointstyle") # TODO: make this an enum? - vals = Dict(:auto => 0, :round => 1) + vals = Dict(:miter => 0, :round => 2) # 0 and 2 are shared between this and capstyle. 1 has no equivalent here return get(vals, value) do error("$value is not a valid joint style. It must be one of $(keys(vals)).") end From 596dada5ca2d2064c210855ca84f92a57a224d4f Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 15:16:28 +0200 Subject: [PATCH 03/37] move code around, add comments --- GLMakie/assets/shader/lines.frag | 38 +++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index 750ab6d3698..a42a9213094 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -129,6 +129,11 @@ float get_pattern_sdf(Nothing _, vec2 uv){ void write2framebuffer(vec4 color, uvec2 id); +// General Notes on SDFs here: +// < 0 is considered inside the shape, i.e. drawn +// min(sdf1, sdf2) is the union shapes sdf1 and sdf2 +// max(sdf1, sdf2) is the intersection of sdf1 and sdf2 + void main(){ vec4 color; @@ -148,38 +153,45 @@ if (!debug) { if (dist_in_prev < f_quad_sdf1.x || dist_in_next < f_quad_sdf1.y) discard; - // SDF for inside vs outside along the line direction. extrusion adjusts - // the distance from p1/p2 for joints etc float sdf; + // f_quad_sdf1.x includes everything from p1 in p2-p1 direction, i.e. > + // f_quad_sdf2.y includes everything from p2 in p1-p2 direction, i.e. < + // < < | > < > < | > > + // < < 1->----<->----<-2 > > + // < < | > < > < | > > if (f_capmode.x == 2) { // rounded joint or cap + // in circle(p1, halfwidth) || is beyond p1 in p2-p1 direction sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); } else if (f_capmode.x == 1) { // :square cap + // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) sdf = f_quad_sdf1.x - f_linewidth; - } else // default miter joint / :butt cap + } else { // default miter joint / :butt cap + // variable shift in -(p2-p1) direction to make space for joints sdf = f_quad_sdf1.x - f_extrusion.x; + // do truncate joints + sdf = max(sdf, f_truncation.x); + } + // Same as above but for p2 if (f_capmode.y == 2) { // rounded joint or cap sdf = max(sdf, min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) ); } else if (f_capmode.y == 1) { // :square cap sdf = max(sdf, f_quad_sdf1.y - f_linewidth); - } else // default miter joint / :butt cap + } else { // default miter joint / :butt cap sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); - - - + sdf = max(sdf, f_truncation.y); + } // distance in linewidth direction + // f_quad_sdf.z is 0 along the line connecting p1 and p2 and increases along line-normal direction + // ^ | ^ ^ | ^ + // 1------------2 + // ^ | ^ ^ | ^ sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); - // outer truncation of truncated joints (smooth outside edge) - if (f_capmode.x != 2) - sdf = max(sdf, f_truncation.x); - if (f_capmode.y != 2) - sdf = max(sdf, f_truncation.y); - // inner truncation (AA for overlapping parts) // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) From e779b97f23654f21abe5e86f0ffad0620dfcaa5a Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 15:28:51 +0200 Subject: [PATCH 04/37] add capstyle for linesegments --- GLMakie/assets/shader/line_segment.geom | 13 ++++++++----- MakieCore/src/basic_plots.jl | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/GLMakie/assets/shader/line_segment.geom b/GLMakie/assets/shader/line_segment.geom index 270c4dc8720..12c5b39233c 100644 --- a/GLMakie/assets/shader/line_segment.geom +++ b/GLMakie/assets/shader/line_segment.geom @@ -11,6 +11,7 @@ layout(triangle_strip, max_vertices = 4) out; uniform vec2 resolution; uniform float pattern_length; {{pattern_type}} pattern; +uniform int capstyle; in {{stripped_color_type}} g_color[]; in uvec2 g_id[]; @@ -80,8 +81,8 @@ void main(void) vec2 n1 = normal_vector(v1); // Set invalid / ignored outputs - f_quad_sdf0 = 10.0; // no joint to previous segment - f_quad_sdf2 = 10.0; // not joint to next segment + f_quad_sdf0 = 1e12; // no joint to previous segment + f_quad_sdf2 = 1e12; // not joint to next segment f_truncation = vec2(-10.0); // no truncated joint f_pattern_overwrite = vec4(-1e12, 1.0, 1e12, 1.0); // no joints to overwrite f_extrusion = vec2(0.5); // no joints needing extrusion @@ -94,15 +95,17 @@ void main(void) f_linestart = 0; // no corners so no joint extrusion to consider f_linelength = segment_length; // and also no changes in line length f_cumulative_length = 0.0; // resets for each new segment - f_capmode = ivec2(0,0); // TODO + + // linecaps + f_capmode = ivec2(capstyle); // Generate vertices for (int x = 0; x < 2; x++) { - // Get offset in line direction - float v_offset = (2 * x - 1) * AA_THICKNESS; // pass on linewidth and id (picking) for the current line vertex float halfwidth = 0.5 * max(AA_RADIUS, g_thickness[x]); + // Get offset in line direction + float v_offset = (2 * x - 1) * (halfwidth + AA_THICKNESS); // TODO: if we just make this a varying output we probably get var linewidths here f_linewidth = halfwidth; f_id = g_id[x]; diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index 49210d66d83..22ce34fddae 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -349,6 +349,8 @@ $(Base.Docs.doc(MakieCore.generic_plot_attributes!)) linewidth = @inherit linewidth "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" linestyle = nothing + "Sets the type of linecap used, i.e. :butt (flat with no extrusion), :square (flat with 1 linewidth extrusion) or :round." + capstyle = :butt "Sets which attributes to cycle when creating multiple plots." cycle = [:color] mixin_generic_plot_attributes()... From 8e4ceed26031daaebedfa93175cdd41b7fdc33fe Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 15:49:56 +0200 Subject: [PATCH 05/37] update WGLMakie --- WGLMakie/src/Lines.js | 65 +++++++++++++++++++++++++------- WGLMakie/src/lines.jl | 8 +++- WGLMakie/src/wglmakie.bundled.js | 65 +++++++++++++++++++++++++------- 3 files changed, 110 insertions(+), 28 deletions(-) diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index ab32f8fb887..4465fbd5340 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -58,7 +58,6 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { ${attribute_decl} - out highp float f_quad_sdf0; // invalid / not needed out highp vec3 f_quad_sdf1; out highp float f_quad_sdf2; // invalid / not needed @@ -75,6 +74,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out ${color} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; + flat out ivec2 f_capmode; ${uniform_decl} @@ -172,8 +172,11 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { //////////////////////////////////////////////////////////////////// + // linecaps + f_capmode = ivec2(capstyle); + // Vertex position (padded for joint & anti-aliasing) - float v_offset = position.x * (0.5 * segment_length + AA_THICKNESS); + float v_offset = position.x * (0.5 * segment_length + halfwidth + AA_THICKNESS); float n_offset = (halfwidth + AA_THICKNESS) * position.y; vec3 point = 0.5 * (p1 + p2) + v_offset * v1 + n_offset * vec3(n1, 0); @@ -182,7 +185,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 VP2 = point.xy - p2.xy; // invalid - no joint to compute overlap with - f_quad_sdf0 = 10.0; + f_quad_sdf0 = 1e12; // sdf of this segment f_quad_sdf1.x = dot(VP1, -v1.xy); @@ -190,10 +193,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { f_quad_sdf1.z = dot(VP1, n1); // invalid - no joint to compute overlap with - f_quad_sdf2 = 10.0; + f_quad_sdf2 = 1e12; // invalid - no joint to truncate - f_truncation = vec2(-10.0); + f_truncation = vec2(-1e12); // simplified - no extrusion or joints means we just have: f_linestart = 0.0; @@ -235,6 +238,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out ${color} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; + flat out ivec2 f_capmode; ${uniform_decl} @@ -550,6 +554,12 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { f_cumulative_length = lastlen_start; + // linecap + jointstyle + f_capmode = ivec2( + isvalid[0] ? jointstyle : capstyle, + isvalid[3] ? jointstyle : capstyle + ); + //////////////////////////////////////////////////////////////////// // Varying vertex data @@ -562,7 +572,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { if (is_truncated[x] || !isvalid[3 * x]) { // handle overlap in fragment shader via SDF comparison offset = shape_factor * ( - (halfwidth * extrusion[x] + position.x * AA_THICKNESS) * v1 + + position.x * (halfwidth * max(1.0, abs(extrusion[x])) + AA_THICKNESS) * v1 + vec3(position.y * (halfwidth + AA_THICKNESS) * n1, 0) ); } else { @@ -671,6 +681,7 @@ function lines_fragment_shader(uniforms, attributes) { flat in float f_alpha_weight; flat in uint f_instance_id; flat in float f_cumulative_length; + flat in ivec2 f_capmode; uniform uint object_id; ${uniform_decl} @@ -793,17 +804,45 @@ function lines_fragment_shader(uniforms, attributes) { if (dist_in_prev < f_quad_sdf1.x || dist_in_next < f_quad_sdf1.y) discard; - // SDF for inside vs outside along the line direction. extrusion adjusts - // the distance from p1/p2 for joints etc - float sdf = max(f_quad_sdf1.x - f_extrusion.x, f_quad_sdf1.y - f_extrusion.y); + float sdf; + + // f_quad_sdf1.x includes everything from p1 in p2-p1 direction, i.e. > + // f_quad_sdf2.y includes everything from p2 in p1-p2 direction, i.e. < + // < < | > < > < | > > + // < < 1->----<->----<-2 > > + // < < | > < > < | > > + if (f_capmode.x == 2) { // rounded joint or cap + // in circle(p1, halfwidth) || is beyond p1 in p2-p1 direction + sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); + } else if (f_capmode.x == 1) { // :square cap + // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) + sdf = f_quad_sdf1.x - f_linewidth; + } else { // default miter joint / :butt cap + // variable shift in -(p2-p1) direction to make space for joints + sdf = f_quad_sdf1.x - f_extrusion.x; + // do truncate joints + sdf = max(sdf, f_truncation.x); + } + + // Same as above but for p2 + if (f_capmode.y == 2) { // rounded joint or cap + sdf = max(sdf, + min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) + ); + } else if (f_capmode.y == 1) { // :square cap + sdf = max(sdf, f_quad_sdf1.y - f_linewidth); + } else { // default miter joint / :butt cap + sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); + sdf = max(sdf, f_truncation.y); + } // distance in linewidth direction + // f_quad_sdf.z is 0 along the line connecting p1 and p2 and increases along line-normal direction + // ^ | ^ ^ | ^ + // 1------------2 + // ^ | ^ ^ | ^ sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); - // truncation of truncated joints (creates flat cap) - sdf = max(sdf, f_truncation.x); - sdf = max(sdf, f_truncation.y); - // inner truncation (AA for overlapping parts) // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) diff --git a/WGLMakie/src/lines.jl b/WGLMakie/src/lines.jl index ea0c0b2bb8a..1f58131c317 100644 --- a/WGLMakie/src/lines.jl +++ b/WGLMakie/src/lines.jl @@ -1,11 +1,15 @@ function serialize_three(scene::Scene, plot::Union{Lines, LineSegments}) - Makie.@converted_attribute plot (linewidth, linestyle) + Makie.@converted_attribute plot (linewidth, linestyle, capstyle, jointstyle) uniforms = Dict( :model => map(Makie.patch_model, f32_conversion_obs(plot), plot.model), :depth_shift => plot.depth_shift, - :picking => false + :picking => false, + :capstyle => capstyle ) + if plot isa Lines + uniforms[:jointstyle] = jointstyle + end # TODO: maybe convert nothing to Sampler([-1.0]) to allowed dynamic linestyles? if isnothing(to_value(linestyle)) diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 54e895a3672..9cd259bf373 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21326,7 +21326,6 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { ${attribute_decl} - out highp float f_quad_sdf0; // invalid / not needed out highp vec3 f_quad_sdf1; out highp float f_quad_sdf2; // invalid / not needed @@ -21343,6 +21342,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out ${color} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; + flat out ivec2 f_capmode; ${uniform_decl} @@ -21440,8 +21440,11 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { //////////////////////////////////////////////////////////////////// + // linecaps + f_capmode = ivec2(capstyle); + // Vertex position (padded for joint & anti-aliasing) - float v_offset = position.x * (0.5 * segment_length + AA_THICKNESS); + float v_offset = position.x * (0.5 * segment_length + halfwidth + AA_THICKNESS); float n_offset = (halfwidth + AA_THICKNESS) * position.y; vec3 point = 0.5 * (p1 + p2) + v_offset * v1 + n_offset * vec3(n1, 0); @@ -21450,7 +21453,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 VP2 = point.xy - p2.xy; // invalid - no joint to compute overlap with - f_quad_sdf0 = 10.0; + f_quad_sdf0 = 1e12; // sdf of this segment f_quad_sdf1.x = dot(VP1, -v1.xy); @@ -21458,10 +21461,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { f_quad_sdf1.z = dot(VP1, n1); // invalid - no joint to compute overlap with - f_quad_sdf2 = 10.0; + f_quad_sdf2 = 1e12; // invalid - no joint to truncate - f_truncation = vec2(-10.0); + f_truncation = vec2(-1e12); // simplified - no extrusion or joints means we just have: f_linestart = 0.0; @@ -21498,6 +21501,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out ${color} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; + flat out ivec2 f_capmode; ${uniform_decl} @@ -21813,6 +21817,12 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { f_cumulative_length = lastlen_start; + // linecap + jointstyle + f_capmode = ivec2( + isvalid[0] ? jointstyle : capstyle, + isvalid[3] ? jointstyle : capstyle + ); + //////////////////////////////////////////////////////////////////// // Varying vertex data @@ -21825,7 +21835,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { if (is_truncated[x] || !isvalid[3 * x]) { // handle overlap in fragment shader via SDF comparison offset = shape_factor * ( - (halfwidth * extrusion[x] + position.x * AA_THICKNESS) * v1 + + position.x * (halfwidth * max(1.0, abs(extrusion[x])) + AA_THICKNESS) * v1 + vec3(position.y * (halfwidth + AA_THICKNESS) * n1, 0) ); } else { @@ -21936,6 +21946,7 @@ function lines_fragment_shader(uniforms, attributes) { flat in float f_alpha_weight; flat in uint f_instance_id; flat in float f_cumulative_length; + flat in ivec2 f_capmode; uniform uint object_id; ${uniform_decl} @@ -22058,17 +22069,45 @@ function lines_fragment_shader(uniforms, attributes) { if (dist_in_prev < f_quad_sdf1.x || dist_in_next < f_quad_sdf1.y) discard; - // SDF for inside vs outside along the line direction. extrusion adjusts - // the distance from p1/p2 for joints etc - float sdf = max(f_quad_sdf1.x - f_extrusion.x, f_quad_sdf1.y - f_extrusion.y); + float sdf; + + // f_quad_sdf1.x includes everything from p1 in p2-p1 direction, i.e. > + // f_quad_sdf2.y includes everything from p2 in p1-p2 direction, i.e. < + // < < | > < > < | > > + // < < 1->----<->----<-2 > > + // < < | > < > < | > > + if (f_capmode.x == 2) { // rounded joint or cap + // in circle(p1, halfwidth) || is beyond p1 in p2-p1 direction + sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); + } else if (f_capmode.x == 1) { // :square cap + // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) + sdf = f_quad_sdf1.x - f_linewidth; + } else { // default miter joint / :butt cap + // variable shift in -(p2-p1) direction to make space for joints + sdf = f_quad_sdf1.x - f_extrusion.x; + // do truncate joints + sdf = max(sdf, f_truncation.x); + } + + // Same as above but for p2 + if (f_capmode.y == 2) { // rounded joint or cap + sdf = max(sdf, + min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) + ); + } else if (f_capmode.y == 1) { // :square cap + sdf = max(sdf, f_quad_sdf1.y - f_linewidth); + } else { // default miter joint / :butt cap + sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); + sdf = max(sdf, f_truncation.y); + } // distance in linewidth direction + // f_quad_sdf.z is 0 along the line connecting p1 and p2 and increases along line-normal direction + // ^ | ^ ^ | ^ + // 1------------2 + // ^ | ^ ^ | ^ sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); - // truncation of truncated joints (creates flat cap) - sdf = max(sdf, f_truncation.x); - sdf = max(sdf, f_truncation.y); - // inner truncation (AA for overlapping parts) // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) From a71b34ac1a35f36bfd7017ab0dbb1058ca96252c Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 15:50:46 +0200 Subject: [PATCH 06/37] revert change in padding of uncapped lines --- GLMakie/assets/shader/lines.geom | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 7496fc60023..b4bc47baf7b 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -376,8 +376,8 @@ void main(void) // if joint skipped elongate to new length // if normal joint elongate a lot to let discard/truncation handle joint f_extrusion = vec2( - !isvalid[0] ? 0.0 : (adjustment[0] == 0.0 ? 1e12 : halfwidth * abs(extrusion[0][0])), - !isvalid[3] ? 0.0 : (adjustment[1] == 0.0 ? 1e12 : halfwidth * abs(extrusion[1][0])) + !isvalid[0] ? min(AA_RADIUS, halfwidth) : (adjustment[0] == 0.0 ? 1e12 : halfwidth * abs(extrusion[0][0])), + !isvalid[3] ? min(AA_RADIUS, halfwidth) : (adjustment[1] == 0.0 ? 1e12 : halfwidth * abs(extrusion[1][0])) ); // used to compute width sdf From 1fc2dd8229c8582ca82144d9528d56b4fbcaa7cd Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 15:51:39 +0200 Subject: [PATCH 07/37] make sure truncation can't trigger --- GLMakie/assets/shader/line_segment.geom | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GLMakie/assets/shader/line_segment.geom b/GLMakie/assets/shader/line_segment.geom index 12c5b39233c..b281670df12 100644 --- a/GLMakie/assets/shader/line_segment.geom +++ b/GLMakie/assets/shader/line_segment.geom @@ -83,7 +83,7 @@ void main(void) // Set invalid / ignored outputs f_quad_sdf0 = 1e12; // no joint to previous segment f_quad_sdf2 = 1e12; // not joint to next segment - f_truncation = vec2(-10.0); // no truncated joint + f_truncation = vec2(-1e12); // no truncated joint f_pattern_overwrite = vec4(-1e12, 1.0, 1e12, 1.0); // no joints to overwrite f_extrusion = vec2(0.5); // no joints needing extrusion f_discard_limit = vec2(10.0); // no joints needing discards From f9d2a0a41f8715d64d0ad976beb70a6f5b7e8a32 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 16:12:04 +0200 Subject: [PATCH 08/37] update CairoMakie --- CairoMakie/src/primitives.jl | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 212678b74d2..e8cd8b7da8a 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -124,6 +124,27 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio Cairo.set_dash(ctx, pattern) end + capstyle = primitive.capstyle[] + if capstyle == :square + Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_SQUARE) + elseif capstyle == :round + Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_ROUND) + else # :butt + Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_BUTT) + end + + # TODO everywhere + # Cairo.set_miter_limit(...) + # "Cairo divides the length of the miter by the line width. If the result is greater than the miter limit, the style is converted to a bevel." + jointstyle = to_value(get(primitive, :jointstyle, :miter)) + if jointstyle == :round + Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_ROUND) + elseif jointstyle == :bevel # TODO in GL backends + Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_BEVEL) + else # :miter + Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_MITER) + end + if primitive isa Lines && to_value(primitive.args[1]) isa BezierPath return draw_bezierpath_lines(ctx, to_value(primitive.args[1]), primitive, color, space, model, linewidth) end @@ -131,11 +152,6 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio if color isa AbstractArray || linewidth isa AbstractArray # stroke each segment separately, this means disjointed segments with probably # wonky dash patterns if segments are short - - # Butted segments look the best for varying colors, at least when connection angles are small. - # While round style has nicer sharp joins, it looks bad with alpha colors (double paint) and - # also messes with dash patterns (they are too long because of the caps) - Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_BUTT) draw_multi( primitive, ctx, projected_positions, From 67eff0dbf550a3fc7d9bf6aae32082efbb769cd5 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 17:59:14 +0200 Subject: [PATCH 09/37] add :bevel --- CairoMakie/src/primitives.jl | 2 +- GLMakie/assets/shader/lines.frag | 6 +++--- GLMakie/assets/shader/lines.geom | 11 ++++++----- WGLMakie/src/Lines.js | 12 ++++++++---- WGLMakie/src/wglmakie.bundled.js | 12 ++++++++---- src/conversions.jl | 3 ++- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index e8cd8b7da8a..6980acbd208 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -139,7 +139,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio jointstyle = to_value(get(primitive, :jointstyle, :miter)) if jointstyle == :round Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_ROUND) - elseif jointstyle == :bevel # TODO in GL backends + elseif jointstyle == :bevel Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_BEVEL) else # :miter Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_MITER) diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index a42a9213094..3be03542984 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -166,7 +166,7 @@ if (!debug) { } else if (f_capmode.x == 1) { // :square cap // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) sdf = f_quad_sdf1.x - f_linewidth; - } else { // default miter joint / :butt cap + } else { // miter or bevel joint or :butt cap // variable shift in -(p2-p1) direction to make space for joints sdf = f_quad_sdf1.x - f_extrusion.x; // do truncate joints @@ -180,7 +180,7 @@ if (!debug) { ); } else if (f_capmode.y == 1) { // :square cap sdf = max(sdf, f_quad_sdf1.y - f_linewidth); - } else { // default miter joint / :butt cap + } else { // miter or bevel joint or :butt cap sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); sdf = max(sdf, f_truncation.y); } @@ -196,7 +196,7 @@ if (!debug) { // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) // and b is the (smoothly) cut of part just after discard triggers (i.e not visible) - // 100.0x sdf makes the sdf much more sharply, avoiding overdraw in the center + // 100.0x sdf makes the sdf much more sharp, avoiding overdraw in the center sdf = max(sdf, min(f_quad_sdf1.x + 1.0, 100.0 * (f_quad_sdf1.x - f_quad_sdf0) - 1.0)); sdf = max(sdf, min(f_quad_sdf1.y + 1.0, 100.0 * (f_quad_sdf1.y - f_quad_sdf2) - 1.0)); diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index b4bc47baf7b..7ab3de93c6c 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -286,11 +286,8 @@ void main(void) vec2 n1 = normal_vector(v1); vec2 n2 = normal_vector(v2); - // Are we truncating the joint? - bvec2 is_truncated = bvec2( - dot(v0, v1.xy) < MITER_LIMIT, - dot(v1.xy, v2) < MITER_LIMIT - ); + // Are we truncating the joint based on miter limit? + bvec2 is_truncated = bvec2(dot(v0, v1.xy) < MITER_LIMIT, dot(v1.xy, v2) < MITER_LIMIT); // Miter normals (normal of truncated edge / vector to sharp corner) // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the @@ -298,6 +295,10 @@ void main(void) vec2 miter_n1 = is_truncated[0] ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); vec2 miter_n2 = is_truncated[1] ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); + // Are we truncating based on jointstyle? (bevel) + if (jointstyle == 3) + is_truncated = bvec2(isvalid[0], isvalid[1]); + // miter vectors (line vector matching miter normal) vec2 miter_v1 = -normal_vector(miter_n1); vec2 miter_v2 = -normal_vector(miter_n2); diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index 4465fbd5340..faf3fd290e7 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -442,7 +442,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // joint information - // Are we truncating the joint? + // Are we truncating the joint based on miter limit? bool[2] is_truncated = bool[2]( dot(v0.xy, v1.xy) < MITER_LIMIT, dot(v1.xy, v2.xy) < MITER_LIMIT @@ -454,6 +454,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 miter_n1 = is_truncated[0] ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); vec2 miter_n2 = is_truncated[1] ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); + // Are we truncating based on jointstyle? (bevel) + if (int(jointstyle) == 3) // why int + is_truncated = bool[2](isvalid[0], isvalid[3]); + // miter vectors (line vector matching miter normal) vec2 miter_v1 = -normal_vector(miter_n1); vec2 miter_v2 = -normal_vector(miter_n2); @@ -817,7 +821,7 @@ function lines_fragment_shader(uniforms, attributes) { } else if (f_capmode.x == 1) { // :square cap // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) sdf = f_quad_sdf1.x - f_linewidth; - } else { // default miter joint / :butt cap + } else { // miter or bevel joint or :butt cap // variable shift in -(p2-p1) direction to make space for joints sdf = f_quad_sdf1.x - f_extrusion.x; // do truncate joints @@ -831,7 +835,7 @@ function lines_fragment_shader(uniforms, attributes) { ); } else if (f_capmode.y == 1) { // :square cap sdf = max(sdf, f_quad_sdf1.y - f_linewidth); - } else { // default miter joint / :butt cap + } else { // miter or bevel joint or :butt cap sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); sdf = max(sdf, f_truncation.y); } @@ -847,7 +851,7 @@ function lines_fragment_shader(uniforms, attributes) { // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) // and b is the (smoothly) cut of part where the discard triggers - // 100.0x sdf makes the sdf much more sharply, avoiding overdraw in the center + // 100.0x sdf makes the sdf much more sharp, avoiding overdraw in the center sdf = max(sdf, min(f_quad_sdf1.x + 1.0, 100.0 * (f_quad_sdf1.x - f_quad_sdf0) - 1.0)); sdf = max(sdf, min(f_quad_sdf1.y + 1.0, 100.0 * (f_quad_sdf1.y - f_quad_sdf2) - 1.0)); diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 9cd259bf373..33f242d499b 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21705,7 +21705,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // joint information - // Are we truncating the joint? + // Are we truncating the joint based on miter limit? bool[2] is_truncated = bool[2]( dot(v0.xy, v1.xy) < MITER_LIMIT, dot(v1.xy, v2.xy) < MITER_LIMIT @@ -21717,6 +21717,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 miter_n1 = is_truncated[0] ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); vec2 miter_n2 = is_truncated[1] ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); + // Are we truncating based on jointstyle? (bevel) + if (int(jointstyle) == 3) // why int + is_truncated = bool[2](isvalid[0], isvalid[3]); + // miter vectors (line vector matching miter normal) vec2 miter_v1 = -normal_vector(miter_n1); vec2 miter_v2 = -normal_vector(miter_n2); @@ -22082,7 +22086,7 @@ function lines_fragment_shader(uniforms, attributes) { } else if (f_capmode.x == 1) { // :square cap // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) sdf = f_quad_sdf1.x - f_linewidth; - } else { // default miter joint / :butt cap + } else { // miter or bevel joint or :butt cap // variable shift in -(p2-p1) direction to make space for joints sdf = f_quad_sdf1.x - f_extrusion.x; // do truncate joints @@ -22096,7 +22100,7 @@ function lines_fragment_shader(uniforms, attributes) { ); } else if (f_capmode.y == 1) { // :square cap sdf = max(sdf, f_quad_sdf1.y - f_linewidth); - } else { // default miter joint / :butt cap + } else { // miter or bevel joint or :butt cap sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); sdf = max(sdf, f_truncation.y); } @@ -22112,7 +22116,7 @@ function lines_fragment_shader(uniforms, attributes) { // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) // and b is the (smoothly) cut of part where the discard triggers - // 100.0x sdf makes the sdf much more sharply, avoiding overdraw in the center + // 100.0x sdf makes the sdf much more sharp, avoiding overdraw in the center sdf = max(sdf, min(f_quad_sdf1.x + 1.0, 100.0 * (f_quad_sdf1.x - f_quad_sdf0) - 1.0)); sdf = max(sdf, min(f_quad_sdf1.y + 1.0, 100.0 * (f_quad_sdf1.y - f_quad_sdf2) - 1.0)); diff --git a/src/conversions.jl b/src/conversions.jl index e804abcf729..fc654ba1322 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -1081,7 +1081,8 @@ function convert_attribute(value::Symbol, ::key"capstyle") end function convert_attribute(value::Symbol, ::key"jointstyle") # TODO: make this an enum? - vals = Dict(:miter => 0, :round => 2) # 0 and 2 are shared between this and capstyle. 1 has no equivalent here + # 0 and 2 are shared between this and capstyle. 1 has no equivalent here + vals = Dict(:miter => 0, :round => 2, :bevel => 3) return get(vals, value) do error("$value is not a valid joint style. It must be one of $(keys(vals)).") end From bd3c3db5dbc1233689d73c5f98cf7e6215c00e68 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 21:51:56 +0200 Subject: [PATCH 10/37] make miter_limit adjustable --- CairoMakie/src/primitives.jl | 7 ++++--- GLMakie/assets/shader/lines.geom | 20 ++++++++++---------- GLMakie/src/drawing_primitives.jl | 2 ++ MakieCore/src/basic_plots.jl | 2 ++ WGLMakie/src/Lines.js | 20 ++++++++------------ WGLMakie/src/lines.jl | 5 ++++- WGLMakie/src/wglmakie.bundled.js | 20 ++++++++------------ 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 6980acbd208..be86af4cba7 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -124,6 +124,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio Cairo.set_dash(ctx, pattern) end + # linecap capstyle = primitive.capstyle[] if capstyle == :square Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_SQUARE) @@ -133,9 +134,9 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_BUTT) end - # TODO everywhere - # Cairo.set_miter_limit(...) - # "Cairo divides the length of the miter by the line width. If the result is greater than the miter limit, the style is converted to a bevel." + # joint style + # TODO: Cairo should have a function like this, but it's not available + # Cairo.set_miter_limit(ctx, plot.miter_limit[]) jointstyle = to_value(get(primitive, :jointstyle, :miter)) if jointstyle == :round Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_ROUND) diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 7ab3de93c6c..b0670dfbcdc 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -44,9 +44,9 @@ uniform vec2 resolution; uniform int capstyle; uniform int jointstyle; +uniform float miter_limit; // Constants -const float MITER_LIMIT = -0.4; const float AA_RADIUS = 0.8; const float AA_THICKNESS = 4.0 * AA_RADIUS; // NOTE: if MITER_LIMIT becomes a variable AA_THICKNESS needs to scale with the joint extrusion @@ -286,18 +286,18 @@ void main(void) vec2 n1 = normal_vector(v1); vec2 n2 = normal_vector(v2); - // Are we truncating the joint based on miter limit? - bvec2 is_truncated = bvec2(dot(v0, v1.xy) < MITER_LIMIT, dot(v1.xy, v2) < MITER_LIMIT); - // Miter normals (normal of truncated edge / vector to sharp corner) // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead - vec2 miter_n1 = is_truncated[0] ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = is_truncated[1] ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); - - // Are we truncating based on jointstyle? (bevel) - if (jointstyle == 3) - is_truncated = bvec2(isvalid[0], isvalid[1]); + vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); + vec2 miter_n1 = miter.x < -0.4 ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < -0.4 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); + + // Are we truncating the joint based on miter limit or jointstyle? + bvec2 is_truncated = bvec2( + (jointstyle == 3) ? isvalid[0] : miter.x < miter_limit, + (jointstyle == 3) ? isvalid[3] : miter.y < miter_limit + ); // miter vectors (line vector matching miter normal) vec2 miter_v1 = -normal_vector(miter_n1); diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 486da3cbf47..0216ab7dbbe 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -443,7 +443,9 @@ end function draw_atomic(screen::Screen, scene::Scene, @nospecialize(plot::Lines)) return cached_robj!(screen, scene, plot) do gl_attributes linestyle = pop!(gl_attributes, :linestyle) + miter_limit = pop!(gl_attributes, :miter_limit) data = Dict{Symbol, Any}(gl_attributes) + data[:miter_limit] = map(x -> Float32(cos(2 * (0.5pi - atan(1 / x)))), plot, miter_limit) positions = handle_view(plot[1], data) space = plot.space diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index 64aca920836..f1acf598993 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -313,6 +313,8 @@ Creates a connected line plot for each element in `(x, y, z)`, `(x, y)` or `posi capstyle = :butt "Controls whether line joints are rounded (:round) or not (:miter)." jointstyle = :miter + "Sets how far a miter joint can extend (in units of linewidth) before being truncated." + miter_limit = 1.5275252316519468 "Sets which attributes to cycle when creating multiple plots." cycle = [:color] mixin_generic_plot_attributes()... diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index faf3fd290e7..dcc3deb154a 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -243,7 +243,6 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { ${uniform_decl} // Constants - const float MITER_LIMIT = -0.4; const float AA_RADIUS = 0.8; const float AA_THICKNESS = 2.0 * AA_RADIUS; @@ -442,21 +441,18 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // joint information - // Are we truncating the joint based on miter limit? - bool[2] is_truncated = bool[2]( - dot(v0.xy, v1.xy) < MITER_LIMIT, - dot(v1.xy, v2.xy) < MITER_LIMIT - ); - // Miter normals (normal of truncated edge / vector to sharp corner) // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead - vec2 miter_n1 = is_truncated[0] ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = is_truncated[1] ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); + vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); + vec2 miter_n1 = miter.x < -0.0 ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < -0.0 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); - // Are we truncating based on jointstyle? (bevel) - if (int(jointstyle) == 3) // why int - is_truncated = bool[2](isvalid[0], isvalid[3]); + // Are we truncating the joint based on miter limit or jointstyle? + bool[2] is_truncated = bool[2]( + (int(jointstyle) == 3) ? isvalid[0] : miter.x < miter_limit, + (int(jointstyle) == 3) ? isvalid[3] : miter.y < miter_limit + ); // miter vectors (line vector matching miter normal) vec2 miter_v1 = -normal_vector(miter_n1); diff --git a/WGLMakie/src/lines.jl b/WGLMakie/src/lines.jl index 1f58131c317..c27a42f5592 100644 --- a/WGLMakie/src/lines.jl +++ b/WGLMakie/src/lines.jl @@ -5,10 +5,13 @@ function serialize_three(scene::Scene, plot::Union{Lines, LineSegments}) :model => map(Makie.patch_model, f32_conversion_obs(plot), plot.model), :depth_shift => plot.depth_shift, :picking => false, - :capstyle => capstyle + :capstyle => capstyle, ) if plot isa Lines uniforms[:jointstyle] = jointstyle + uniforms[:miter_limit] = map(plot, plot.miter_limit) do x + return cos(2 * (0.5pi - atan(1 / x))) + end end # TODO: maybe convert nothing to Sampler([-1.0]) to allowed dynamic linestyles? diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 33f242d499b..8a1e6846f8d 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21506,7 +21506,6 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { ${uniform_decl} // Constants - const float MITER_LIMIT = -0.4; const float AA_RADIUS = 0.8; const float AA_THICKNESS = 2.0 * AA_RADIUS; @@ -21705,21 +21704,18 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // joint information - // Are we truncating the joint based on miter limit? - bool[2] is_truncated = bool[2]( - dot(v0.xy, v1.xy) < MITER_LIMIT, - dot(v1.xy, v2.xy) < MITER_LIMIT - ); - // Miter normals (normal of truncated edge / vector to sharp corner) // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead - vec2 miter_n1 = is_truncated[0] ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = is_truncated[1] ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); + vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); + vec2 miter_n1 = miter.x < -0.0 ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < -0.0 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); - // Are we truncating based on jointstyle? (bevel) - if (int(jointstyle) == 3) // why int - is_truncated = bool[2](isvalid[0], isvalid[3]); + // Are we truncating the joint based on miter limit or jointstyle? + bool[2] is_truncated = bool[2]( + (int(jointstyle) == 3) ? isvalid[0] : miter.x < miter_limit, + (int(jointstyle) == 3) ? isvalid[3] : miter.y < miter_limit + ); // miter vectors (line vector matching miter normal) vec2 miter_v1 = -normal_vector(miter_n1); From 1663f9d58b28ca5211786e931c1003e80068ca0d Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 21:58:15 +0200 Subject: [PATCH 11/37] capstyle -> linecap --- CairoMakie/src/primitives.jl | 6 +++--- GLMakie/assets/shader/line_segment.geom | 4 ++-- GLMakie/assets/shader/lines.geom | 6 +++--- MakieCore/src/basic_plots.jl | 4 ++-- WGLMakie/src/Lines.js | 6 +++--- WGLMakie/src/lines.jl | 4 ++-- WGLMakie/src/wglmakie.bundled.js | 6 +++--- src/conversions.jl | 4 ++-- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index be86af4cba7..57e6698147f 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -125,10 +125,10 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio end # linecap - capstyle = primitive.capstyle[] - if capstyle == :square + linecap = primitive.linecap[] + if linecap == :square Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_SQUARE) - elseif capstyle == :round + elseif linecap == :round Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_ROUND) else # :butt Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_BUTT) diff --git a/GLMakie/assets/shader/line_segment.geom b/GLMakie/assets/shader/line_segment.geom index b281670df12..d2d38444477 100644 --- a/GLMakie/assets/shader/line_segment.geom +++ b/GLMakie/assets/shader/line_segment.geom @@ -11,7 +11,7 @@ layout(triangle_strip, max_vertices = 4) out; uniform vec2 resolution; uniform float pattern_length; {{pattern_type}} pattern; -uniform int capstyle; +uniform int linecap; in {{stripped_color_type}} g_color[]; in uvec2 g_id[]; @@ -97,7 +97,7 @@ void main(void) f_cumulative_length = 0.0; // resets for each new segment // linecaps - f_capmode = ivec2(capstyle); + f_capmode = ivec2(linecap); // Generate vertices diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index b0670dfbcdc..5dc1a8ed384 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -42,7 +42,7 @@ out vec3 o_view_normal; uniform float pattern_length; uniform vec2 resolution; -uniform int capstyle; +uniform int linecap; uniform int jointstyle; uniform float miter_limit; @@ -396,8 +396,8 @@ void main(void) // 0 :butt/normal cap or joint | 1 :square cap | 2 rounded cap/joint f_capmode = ivec2( - isvalid[0] ? jointstyle : capstyle, - isvalid[3] ? jointstyle : capstyle + isvalid[0] ? jointstyle : linecap, + isvalid[3] ? jointstyle : linecap ); // Generate interpolated/varying outputs: diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index f1acf598993..79ffe7458ed 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -310,7 +310,7 @@ Creates a connected line plot for each element in `(x, y, z)`, `(x, y)` or `posi "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" linestyle = nothing "Sets the type of linecap used, i.e. :butt (flat with no extrusion), :square (flat with 1 linewidth extrusion) or :round." - capstyle = :butt + linecap = :butt "Controls whether line joints are rounded (:round) or not (:miter)." jointstyle = :miter "Sets how far a miter joint can extend (in units of linewidth) before being truncated." @@ -352,7 +352,7 @@ $(Base.Docs.doc(MakieCore.generic_plot_attributes!)) "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" linestyle = nothing "Sets the type of linecap used, i.e. :butt (flat with no extrusion), :square (flat with 1 linewidth extrusion) or :round." - capstyle = :butt + linecap = :butt "Sets which attributes to cycle when creating multiple plots." cycle = [:color] mixin_generic_plot_attributes()... diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index dcc3deb154a..50f09f34394 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -173,7 +173,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // linecaps - f_capmode = ivec2(capstyle); + f_capmode = ivec2(linecap); // Vertex position (padded for joint & anti-aliasing) float v_offset = position.x * (0.5 * segment_length + halfwidth + AA_THICKNESS); @@ -556,8 +556,8 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // linecap + jointstyle f_capmode = ivec2( - isvalid[0] ? jointstyle : capstyle, - isvalid[3] ? jointstyle : capstyle + isvalid[0] ? jointstyle : linecap, + isvalid[3] ? jointstyle : linecap ); diff --git a/WGLMakie/src/lines.jl b/WGLMakie/src/lines.jl index c27a42f5592..dc0a9218ba2 100644 --- a/WGLMakie/src/lines.jl +++ b/WGLMakie/src/lines.jl @@ -1,11 +1,11 @@ function serialize_three(scene::Scene, plot::Union{Lines, LineSegments}) - Makie.@converted_attribute plot (linewidth, linestyle, capstyle, jointstyle) + Makie.@converted_attribute plot (linewidth, linestyle, linecap, jointstyle) uniforms = Dict( :model => map(Makie.patch_model, f32_conversion_obs(plot), plot.model), :depth_shift => plot.depth_shift, :picking => false, - :capstyle => capstyle, + :linecap => linecap, ) if plot isa Lines uniforms[:jointstyle] = jointstyle diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 8a1e6846f8d..4e89f360d7b 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21441,7 +21441,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // linecaps - f_capmode = ivec2(capstyle); + f_capmode = ivec2(linecap); // Vertex position (padded for joint & anti-aliasing) float v_offset = position.x * (0.5 * segment_length + halfwidth + AA_THICKNESS); @@ -21819,8 +21819,8 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // linecap + jointstyle f_capmode = ivec2( - isvalid[0] ? jointstyle : capstyle, - isvalid[3] ? jointstyle : capstyle + isvalid[0] ? jointstyle : linecap, + isvalid[3] ? jointstyle : linecap ); diff --git a/src/conversions.jl b/src/conversions.jl index fc654ba1322..085b889cdad 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -1072,7 +1072,7 @@ function convert_gaps(gaps::GapType) return (dot_gap = dot_gap, dash_gap = dash_gap) end -function convert_attribute(value::Symbol, ::key"capstyle") +function convert_attribute(value::Symbol, ::key"linecap") # TODO: make this an enum? vals = Dict(:butt => 0, :square => 1, :round => 2) return get(vals, value) do @@ -1081,7 +1081,7 @@ function convert_attribute(value::Symbol, ::key"capstyle") end function convert_attribute(value::Symbol, ::key"jointstyle") # TODO: make this an enum? - # 0 and 2 are shared between this and capstyle. 1 has no equivalent here + # 0 and 2 are shared between this and linecap. 1 has no equivalent here vals = Dict(:miter => 0, :round => 2, :bevel => 3) return get(vals, value) do error("$value is not a valid joint style. It must be one of $(keys(vals)).") From 841efe321f8dfca50cb3bf7dfa3fad1c3319cfa8 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 5 Apr 2024 21:59:22 +0200 Subject: [PATCH 12/37] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a5efe666b..cebb61658e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +- Added `linecap` and `jointstyle` attributes for lines and linesegments [#3771](https://github.com/MakieOrg/Makie.jl/pull/3771) + ## [0.21.0] - 2024-03-0X - Add `voxels` plot [#3527](https://github.com/MakieOrg/Makie.jl/pull/3527) From 3242d1555c828920c5349b55e6298467c8bfa62c Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 7 Apr 2024 15:34:27 +0200 Subject: [PATCH 13/37] add refimg tests --- CairoMakie/test/runtests.jl | 1 + ReferenceTests/src/tests/primitives.jl | 54 +++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/CairoMakie/test/runtests.jl b/CairoMakie/test/runtests.jl index c066b345543..b22980eacba 100644 --- a/CairoMakie/test/runtests.jl +++ b/CairoMakie/test/runtests.jl @@ -187,6 +187,7 @@ excludes = Set([ "Textured meshscatter", # not yet implemented "Voxel - texture mapping", # not yet implemented "Miter Joints for line rendering", # CairoMakie does not show overlap here and extrudes lines a little more + "Miter Limit", # Cairo should support this but it's not available in Cairo.jl ]) functions = [:volume, :volume!, :uv_mesh] diff --git a/ReferenceTests/src/tests/primitives.jl b/ReferenceTests/src/tests/primitives.jl index 459ab08c701..8a7d2c92ae4 100644 --- a/ReferenceTests/src/tests/primitives.jl +++ b/ReferenceTests/src/tests/primitives.jl @@ -1,5 +1,4 @@ @reference_test "lines and linestyles" begin - # For now disabled until we fix GLMakie linestyle s = Scene(size = (800, 800), camera = campixel!) scalar = 30 points = Point2f[(1, 1), (1, 2), (2, 3), (2, 1)] @@ -59,6 +58,59 @@ end scene end +@reference_test "Linecaps and jointstyles" begin + fig = Figure(size = (550, 450)) + ps = [Point2f(0), Point2f(2, 0)] + r = 2.0 + for phi in [130, -114, 113, 90, -60] + R = Makie.Mat2f(cosd(phi), sind(phi), -sind(phi), cosd(phi)) + r += 0.2 + push!(ps, ps[end] + r * R * normalize(ps[end] - ps[end-1])) + end + + for i in 1:3, j in 1:3 + ax = Axis(fig[i, j], aspect = DataAspect()) + hidedecorations!(ax) + xlims!(-4.7, 3.7) + ylims!(-1.0, 5.5) + lines!( + ax, ps, linewidth = 20, + linecap = (:butt, :square, :round)[i], + jointstyle = (:miter, :bevel, :round)[j] + ) + scatterlines!(ax, ps, color = :orange) + end + + fig +end + +@reference_test "Miter Limit" begin + ps = [Point2f(0, -0.5), Point2f(1, -0.5)] + for phi in [160, -130, 114, 60, 113, -90] + R = Makie.Mat2f(cosd(phi), sind(phi), -sind(phi), cosd(phi)) + push!(ps, ps[end] + (1 + 0.2 * (phi == 60)) * R * normalize(ps[end] - ps[end-1])) + end + popfirst!(ps) + + fig = Figure(size = (400, 400)) + ax = Axis(fig[1, 1], aspect = DataAspect()) + hidedecorations!(ax) + xlims!(-2.5, 2.5) + ylims!(-2.5, 2.5) + lines!( + ax, ps .+ Point2f(-1.2, -1.2), linewidth = 20, miter_limit = 1.0, color = :black, + jointstyle = :round + ) + lines!( + ax, ps .+ Point2f(+1.2, -1.2), linewidth = 20, miter_limit = 2.0, color = :black, + jointstyle = :bevel + ) + lines!(ax, ps .+ Point2f(-1.2, +1.2), linewidth = 20, miter_limit = 1.0, color = :black) + lines!(ax, ps .+ Point2f(+1.2, +1.2), linewidth = 20, miter_limit = 2.0, color = :black) + + fig +end + @reference_test "Lines from outside" begin # This tests that lines that start or end in clipped regions are still # rendered correctly. For perspective projections this can be tricky as From e391cc72b7f07b4e0afdf99924ff0cf47eafbe8d Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 7 Apr 2024 15:46:00 +0200 Subject: [PATCH 14/37] add example --- docs/reference/plots/lines.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/reference/plots/lines.md b/docs/reference/plots/lines.md index a41d6688d52..5806587450c 100644 --- a/docs/reference/plots/lines.md +++ b/docs/reference/plots/lines.md @@ -52,6 +52,31 @@ f ``` \end{examplefigure} +### Linecaps and Jointstyles + +\begin{examplefigure}{svg = true} +```julia +using CairoMakie +CairoMakie.activate!() # hide + +f = Figure() +Axis(f[1, 1]) + +ps = 0.8 .* Point2f[(-0.2, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5), (-0.5, -0.2)] + +for i in 1:3, j in 1:3 + lines!( + ps .+ Point2f(i, -j), linewidth = 20, + linecap = (:butt, :square, :round)[i], + jointstyle = (:miter, :bevel, :round)[j] + ) + scatterlines!(ps .+ Point2f(i, -j), color = :gray) +end + +f +``` +\end{examplefigure} + ### Dealing with outline artifacts in GLMakie In GLMakie 3D line plots can generate outline artifacts depending on the order line segments are rendered in. From 9722fc74c2413a1b1a96355a9c80a6657a88c7e6 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 7 Apr 2024 16:08:13 +0200 Subject: [PATCH 15/37] fix rendering issue with bevel for continued lines --- GLMakie/assets/shader/lines.geom | 6 ++++-- ReferenceTests/src/tests/primitives.jl | 2 +- WGLMakie/src/Lines.js | 6 ++++-- WGLMakie/src/wglmakie.bundled.js | 6 ++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 5dc1a8ed384..318e6f4169c 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -294,9 +294,11 @@ void main(void) vec2 miter_n2 = miter.y < -0.4 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or jointstyle? + // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow + // miter joints a when v1 ≈ v2 (v0) bvec2 is_truncated = bvec2( - (jointstyle == 3) ? isvalid[0] : miter.x < miter_limit, - (jointstyle == 3) ? isvalid[3] : miter.y < miter_limit + (jointstyle == 3) ? miter.x < 0.99 : miter.x < miter_limit, + (jointstyle == 3) ? miter.y < 0.99 : miter.y < miter_limit ); // miter vectors (line vector matching miter normal) diff --git a/ReferenceTests/src/tests/primitives.jl b/ReferenceTests/src/tests/primitives.jl index 8a7d2c92ae4..15a6d972419 100644 --- a/ReferenceTests/src/tests/primitives.jl +++ b/ReferenceTests/src/tests/primitives.jl @@ -60,7 +60,7 @@ end @reference_test "Linecaps and jointstyles" begin fig = Figure(size = (550, 450)) - ps = [Point2f(0), Point2f(2, 0)] + ps = [Point2f(-2, 0), Point2f(0), Point2f(2, 0)] r = 2.0 for phi in [130, -114, 113, 90, -60] R = Makie.Mat2f(cosd(phi), sind(phi), -sind(phi), cosd(phi)) diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index 50f09f34394..7a0aee9b968 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -449,9 +449,11 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 miter_n2 = miter.y < -0.0 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or jointstyle? + // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow + // miter joints a when v1 ≈ v2 (v0) bool[2] is_truncated = bool[2]( - (int(jointstyle) == 3) ? isvalid[0] : miter.x < miter_limit, - (int(jointstyle) == 3) ? isvalid[3] : miter.y < miter_limit + (int(jointstyle) == 3) ? miter.x < 0.99 : miter.x < miter_limit, + (int(jointstyle) == 3) ? miter.y < 0.99 : miter.y < miter_limit ); // miter vectors (line vector matching miter normal) diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 4e89f360d7b..fc1ab6772ff 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21712,9 +21712,11 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 miter_n2 = miter.y < -0.0 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or jointstyle? + // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow + // miter joints a when v1 ≈ v2 (v0) bool[2] is_truncated = bool[2]( - (int(jointstyle) == 3) ? isvalid[0] : miter.x < miter_limit, - (int(jointstyle) == 3) ? isvalid[3] : miter.y < miter_limit + (int(jointstyle) == 3) ? miter.x < 0.99 : miter.x < miter_limit, + (int(jointstyle) == 3) ? miter.y < 0.99 : miter.y < miter_limit ); // miter vectors (line vector matching miter normal) From 500a530332f21945b591ecbbb8a267b9f8eb5ac5 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 7 Apr 2024 16:15:50 +0200 Subject: [PATCH 16/37] use named constants --- GLMakie/assets/shader/lines.frag | 13 +++++++++---- GLMakie/assets/shader/lines.geom | 9 +++++++-- WGLMakie/src/Lines.js | 22 ++++++++++++++++------ WGLMakie/src/wglmakie.bundled.js | 22 ++++++++++++++++------ 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index 3be03542984..316a9d44be2 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -41,6 +41,11 @@ uniform vec4 nan_color; // Half width of antialiasing smoothstep const float AA_RADIUS = 0.8; +const int BUTT = 0; +const int SQUARE = 1; +const int ROUND = 2; +const int MITER = 0; +const int BEVEL = 3; float aastep(float threshold1, float dist) { return smoothstep(threshold1-AA_RADIUS, threshold1+AA_RADIUS, dist); @@ -160,10 +165,10 @@ if (!debug) { // < < | > < > < | > > // < < 1->----<->----<-2 > > // < < | > < > < | > > - if (f_capmode.x == 2) { // rounded joint or cap + if (f_capmode.x == ROUND) { // in circle(p1, halfwidth) || is beyond p1 in p2-p1 direction sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); - } else if (f_capmode.x == 1) { // :square cap + } else if (f_capmode.x == SQUARE) { // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) sdf = f_quad_sdf1.x - f_linewidth; } else { // miter or bevel joint or :butt cap @@ -174,11 +179,11 @@ if (!debug) { } // Same as above but for p2 - if (f_capmode.y == 2) { // rounded joint or cap + if (f_capmode.y == ROUND) { // rounded joint or cap sdf = max(sdf, min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) ); - } else if (f_capmode.y == 1) { // :square cap + } else if (f_capmode.y == SQUARE) { // :square cap sdf = max(sdf, f_quad_sdf1.y - f_linewidth); } else { // miter or bevel joint or :butt cap sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 318e6f4169c..64164e6a308 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -50,6 +50,11 @@ uniform float miter_limit; const float AA_RADIUS = 0.8; const float AA_THICKNESS = 4.0 * AA_RADIUS; // NOTE: if MITER_LIMIT becomes a variable AA_THICKNESS needs to scale with the joint extrusion +const int BUTT = 0; +const int SQUARE = 1; +const int ROUND = 2; +const int MITER = 0; +const int BEVEL = 3; vec3 screen_space(vec4 vertex) { return vec3((0.5 * vertex.xy / vertex.w + 0.5) * resolution, vertex.z / vertex.w); @@ -297,8 +302,8 @@ void main(void) // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow // miter joints a when v1 ≈ v2 (v0) bvec2 is_truncated = bvec2( - (jointstyle == 3) ? miter.x < 0.99 : miter.x < miter_limit, - (jointstyle == 3) ? miter.y < 0.99 : miter.y < miter_limit + (jointstyle == BEVEL) ? miter.x < 0.99 : miter.x < miter_limit, + (jointstyle == BEVEL) ? miter.y < 0.99 : miter.y < miter_limit ); // miter vectors (line vector matching miter normal) diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index 7a0aee9b968..5836c7c9419 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -245,6 +245,11 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // Constants const float AA_RADIUS = 0.8; const float AA_THICKNESS = 2.0 * AA_RADIUS; + const int BUTT = 0; + const int SQUARE = 1; + const int ROUND = 2; + const int MITER = 0; + const int BEVEL = 3; //////////////////////////////////////////////////////////////////////// @@ -452,8 +457,8 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow // miter joints a when v1 ≈ v2 (v0) bool[2] is_truncated = bool[2]( - (int(jointstyle) == 3) ? miter.x < 0.99 : miter.x < miter_limit, - (int(jointstyle) == 3) ? miter.y < 0.99 : miter.y < miter_limit + (int(jointstyle) == BEVEL) ? miter.x < 0.99 : miter.x < miter_limit, + (int(jointstyle) == BEVEL) ? miter.y < 0.99 : miter.y < miter_limit ); // miter vectors (line vector matching miter normal) @@ -694,6 +699,11 @@ function lines_fragment_shader(uniforms, attributes) { const float AA_RADIUS = 0.8; // space allocated for AA const float AA_THICKNESS = 2.0 * AA_RADIUS; + const int BUTT = 0; + const int SQUARE = 1; + const int ROUND = 2; + const int MITER = 0; + const int BEVEL = 3; float aastep(float threshold, float value) { return smoothstep(threshold-AA_RADIUS, threshold+AA_RADIUS, value); @@ -813,10 +823,10 @@ function lines_fragment_shader(uniforms, attributes) { // < < | > < > < | > > // < < 1->----<->----<-2 > > // < < | > < > < | > > - if (f_capmode.x == 2) { // rounded joint or cap + if (f_capmode.x == ROUND) { // in circle(p1, halfwidth) || is beyond p1 in p2-p1 direction sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); - } else if (f_capmode.x == 1) { // :square cap + } else if (f_capmode.x == SQUARE) { // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) sdf = f_quad_sdf1.x - f_linewidth; } else { // miter or bevel joint or :butt cap @@ -827,11 +837,11 @@ function lines_fragment_shader(uniforms, attributes) { } // Same as above but for p2 - if (f_capmode.y == 2) { // rounded joint or cap + if (f_capmode.y == ROUND) { sdf = max(sdf, min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) ); - } else if (f_capmode.y == 1) { // :square cap + } else if (f_capmode.y == SQUARE) { sdf = max(sdf, f_quad_sdf1.y - f_linewidth); } else { // miter or bevel joint or :butt cap sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index fc1ab6772ff..ddf5a1282b5 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21508,6 +21508,11 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // Constants const float AA_RADIUS = 0.8; const float AA_THICKNESS = 2.0 * AA_RADIUS; + const int BUTT = 0; + const int SQUARE = 1; + const int ROUND = 2; + const int MITER = 0; + const int BEVEL = 3; //////////////////////////////////////////////////////////////////////// @@ -21715,8 +21720,8 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow // miter joints a when v1 ≈ v2 (v0) bool[2] is_truncated = bool[2]( - (int(jointstyle) == 3) ? miter.x < 0.99 : miter.x < miter_limit, - (int(jointstyle) == 3) ? miter.y < 0.99 : miter.y < miter_limit + (int(jointstyle) == BEVEL) ? miter.x < 0.99 : miter.x < miter_limit, + (int(jointstyle) == BEVEL) ? miter.y < 0.99 : miter.y < miter_limit ); // miter vectors (line vector matching miter normal) @@ -21959,6 +21964,11 @@ function lines_fragment_shader(uniforms, attributes) { const float AA_RADIUS = 0.8; // space allocated for AA const float AA_THICKNESS = 2.0 * AA_RADIUS; + const int BUTT = 0; + const int SQUARE = 1; + const int ROUND = 2; + const int MITER = 0; + const int BEVEL = 3; float aastep(float threshold, float value) { return smoothstep(threshold-AA_RADIUS, threshold+AA_RADIUS, value); @@ -22078,10 +22088,10 @@ function lines_fragment_shader(uniforms, attributes) { // < < | > < > < | > > // < < 1->----<->----<-2 > > // < < | > < > < | > > - if (f_capmode.x == 2) { // rounded joint or cap + if (f_capmode.x == ROUND) { // in circle(p1, halfwidth) || is beyond p1 in p2-p1 direction sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); - } else if (f_capmode.x == 1) { // :square cap + } else if (f_capmode.x == SQUARE) { // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) sdf = f_quad_sdf1.x - f_linewidth; } else { // miter or bevel joint or :butt cap @@ -22092,11 +22102,11 @@ function lines_fragment_shader(uniforms, attributes) { } // Same as above but for p2 - if (f_capmode.y == 2) { // rounded joint or cap + if (f_capmode.y == ROUND) { sdf = max(sdf, min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) ); - } else if (f_capmode.y == 1) { // :square cap + } else if (f_capmode.y == SQUARE) { sdf = max(sdf, f_quad_sdf1.y - f_linewidth); } else { // miter or bevel joint or :butt cap sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); From 42a98331b261471ce8cc7af43a7265969aef25df Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 7 Apr 2024 18:22:33 +0200 Subject: [PATCH 17/37] add more space to test --- ReferenceTests/src/tests/primitives.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReferenceTests/src/tests/primitives.jl b/ReferenceTests/src/tests/primitives.jl index 15a6d972419..abdb431e4c3 100644 --- a/ReferenceTests/src/tests/primitives.jl +++ b/ReferenceTests/src/tests/primitives.jl @@ -71,7 +71,7 @@ end for i in 1:3, j in 1:3 ax = Axis(fig[i, j], aspect = DataAspect()) hidedecorations!(ax) - xlims!(-4.7, 3.7) + xlims!(-4.7, 4.2) ylims!(-1.0, 5.5) lines!( ax, ps, linewidth = 20, From f458c4df8e33b2219af18de57c3ea0490bdea077 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 9 Apr 2024 15:25:56 +0200 Subject: [PATCH 18/37] consider miter_limit in CairoMakie as well --- CairoMakie/src/primitives.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 57e6698147f..f928f83ddcd 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -135,8 +135,8 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio end # joint style - # TODO: Cairo should have a function like this, but it's not available - # Cairo.set_miter_limit(ctx, plot.miter_limit[]) + set_miter_limit(ctx, sqrt(1.0 + to_value(get(primitive, :miter_limit, 1.5275252316519468))^2)) + jointstyle = to_value(get(primitive, :jointstyle, :miter)) if jointstyle == :round Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_ROUND) From 249aaad6e0323736bb6b8f3fb926ebd269c7bd3a Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 9 Apr 2024 15:38:38 +0200 Subject: [PATCH 19/37] also enable refimg test --- CairoMakie/test/runtests.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CairoMakie/test/runtests.jl b/CairoMakie/test/runtests.jl index b22980eacba..20e8b1c5832 100644 --- a/CairoMakie/test/runtests.jl +++ b/CairoMakie/test/runtests.jl @@ -186,8 +186,7 @@ excludes = Set([ "heatmaps & surface", "Textured meshscatter", # not yet implemented "Voxel - texture mapping", # not yet implemented - "Miter Joints for line rendering", # CairoMakie does not show overlap here and extrudes lines a little more - "Miter Limit", # Cairo should support this but it's not available in Cairo.jl + "Miter Joints for line rendering", # CairoMakie does not show overlap here ]) functions = [:volume, :volume!, :uv_mesh] From 43ba4ef181515adf5f2172b2387f3b7b6f50059f Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 10 Apr 2024 12:33:41 +0200 Subject: [PATCH 20/37] switch to angle based miter_limit --- CairoMakie/src/primitives.jl | 3 ++- GLMakie/src/drawing_primitives.jl | 2 +- MakieCore/src/basic_plots.jl | 4 ++-- WGLMakie/src/lines.jl | 4 +--- src/conversions.jl | 32 +++++++++++++++++++++++++++++++ 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index f928f83ddcd..bdbe8e98570 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -135,7 +135,8 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio end # joint style - set_miter_limit(ctx, sqrt(1.0 + to_value(get(primitive, :miter_limit, 1.5275252316519468))^2)) + miter_angle = to_value(get(primitive, :miter_limit, 2pi/3)) + set_miter_limit(ctx, 2.0 * Makie.miter_angle_to_distance(miter_angle)) jointstyle = to_value(get(primitive, :jointstyle, :miter)) if jointstyle == :round diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 0216ab7dbbe..d51d3c7ad25 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -445,7 +445,7 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(plot::Lines)) linestyle = pop!(gl_attributes, :linestyle) miter_limit = pop!(gl_attributes, :miter_limit) data = Dict{Symbol, Any}(gl_attributes) - data[:miter_limit] = map(x -> Float32(cos(2 * (0.5pi - atan(1 / x)))), plot, miter_limit) + data[:miter_limit] = map(x -> Float32(cos(pi - x)), plot, miter_limit) positions = handle_view(plot[1], data) space = plot.space diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index 79ffe7458ed..3d8249536a5 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -313,8 +313,8 @@ Creates a connected line plot for each element in `(x, y, z)`, `(x, y)` or `posi linecap = :butt "Controls whether line joints are rounded (:round) or not (:miter)." jointstyle = :miter - "Sets how far a miter joint can extend (in units of linewidth) before being truncated." - miter_limit = 1.5275252316519468 + "Sets the minimum inner joint angle below which miter joints truncate. See also `Makie.miter_distance_to_angle()`" + miter_limit = 2pi/3 "Sets which attributes to cycle when creating multiple plots." cycle = [:color] mixin_generic_plot_attributes()... diff --git a/WGLMakie/src/lines.jl b/WGLMakie/src/lines.jl index dc0a9218ba2..5ef1db2fbf1 100644 --- a/WGLMakie/src/lines.jl +++ b/WGLMakie/src/lines.jl @@ -9,9 +9,7 @@ function serialize_three(scene::Scene, plot::Union{Lines, LineSegments}) ) if plot isa Lines uniforms[:jointstyle] = jointstyle - uniforms[:miter_limit] = map(plot, plot.miter_limit) do x - return cos(2 * (0.5pi - atan(1 / x))) - end + uniforms[:miter_limit] = map(x -> cos(pi - x), plot, plot.miter_limit) end # TODO: maybe convert nothing to Sampler([-1.0]) to allowed dynamic linestyles? diff --git a/src/conversions.jl b/src/conversions.jl index 085b889cdad..f323ed0e196 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -1079,6 +1079,7 @@ function convert_attribute(value::Symbol, ::key"linecap") error("$value is not a valid cap style. It must be one of $(keys(vals)).") end end + function convert_attribute(value::Symbol, ::key"jointstyle") # TODO: make this an enum? # 0 and 2 are shared between this and linecap. 1 has no equivalent here @@ -1088,6 +1089,37 @@ function convert_attribute(value::Symbol, ::key"jointstyle") end end +""" + miter_distance_to_angle(distance[, directed = false]) + +Calculates the inner angle between two joined line segments corresponding to the +distance between the line point (corner at linewidth → 0) and the outer corner. +The distance is given in units of linewidth. If `directed = true` the distance +in line direction is used instead. + +With respect to miter limits, a line joint gets truncated if the corner distance +exceeds a given limit or analogously the angle falls below a certain limit. This +function calculates the angle given a distance. +""" +function miter_distance_to_angle(distance, directed = false) + if directed + distance < 0.0 && error("The directed distance cannot be negative.") + return 2.0 * atan(0.5 / distance) + else # Cairo style + distance < 0.5 && error("The undirected distance cannot be smaller than 0.5 as the outer corner is always at least half a linewidth away from the line point.") + return 2.0 * asin(0.5 / distance) + end +end + +""" + miter_angle_to_distance(angle, directed = false) + +Calculates the inverse of `miter_distance_to_angle` with an angle given in radians. +""" +function miter_angle_to_distance(angle, directed = false) + 0.0 < angle < pi || error("Angle must be between 0 and pi.") + return directed ? 0.5 / tan(0.5 * angle) : 0.5 / sin(0.5 * angle) +end convert_attribute(c::Tuple{<: Number, <: Number}, ::key"position") = Point2f(c[1], c[2]) From 853153cb8e9b07d5255c8496c1965276648c6e93 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 10 Apr 2024 12:49:31 +0200 Subject: [PATCH 21/37] fix default --- MakieCore/src/basic_plots.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index 3d8249536a5..db49b9c3eb2 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -314,7 +314,7 @@ Creates a connected line plot for each element in `(x, y, z)`, `(x, y)` or `posi "Controls whether line joints are rounded (:round) or not (:miter)." jointstyle = :miter "Sets the minimum inner joint angle below which miter joints truncate. See also `Makie.miter_distance_to_angle()`" - miter_limit = 2pi/3 + miter_limit = pi/3 "Sets which attributes to cycle when creating multiple plots." cycle = [:color] mixin_generic_plot_attributes()... From 284d5cab183fd056bc382c187c3fa27860203895 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 10 Apr 2024 12:49:50 +0200 Subject: [PATCH 22/37] tweak tests --- ReferenceTests/src/tests/primitives.jl | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ReferenceTests/src/tests/primitives.jl b/ReferenceTests/src/tests/primitives.jl index abdb431e4c3..c709db8f0be 100644 --- a/ReferenceTests/src/tests/primitives.jl +++ b/ReferenceTests/src/tests/primitives.jl @@ -62,7 +62,7 @@ end fig = Figure(size = (550, 450)) ps = [Point2f(-2, 0), Point2f(0), Point2f(2, 0)] r = 2.0 - for phi in [130, -114, 113, 90, -60] + for phi in [130, -121, 119, 90, -60] R = Makie.Mat2f(cosd(phi), sind(phi), -sind(phi), cosd(phi)) r += 0.2 push!(ps, ps[end] + r * R * normalize(ps[end] - ps[end-1])) @@ -73,7 +73,7 @@ end hidedecorations!(ax) xlims!(-4.7, 4.2) ylims!(-1.0, 5.5) - lines!( + p = lines!( ax, ps, linewidth = 20, linecap = (:butt, :square, :round)[i], jointstyle = (:miter, :bevel, :round)[j] @@ -84,29 +84,30 @@ end fig end -@reference_test "Miter Limit" begin +#@reference_test "Miter Limit" +begin ps = [Point2f(0, -0.5), Point2f(1, -0.5)] - for phi in [160, -130, 114, 60, 113, -90] + for phi in [160, -130, 121, 50, 119, -90] # these are 180-miter_angle R = Makie.Mat2f(cosd(phi), sind(phi), -sind(phi), cosd(phi)) - push!(ps, ps[end] + (1 + 0.2 * (phi == 60)) * R * normalize(ps[end] - ps[end-1])) + push!(ps, ps[end] + (1 + 0.2 * (phi == 50)) * R * normalize(ps[end] - ps[end-1])) end - popfirst!(ps) + popfirst!(ps) # for alignment, removes 160° corner fig = Figure(size = (400, 400)) ax = Axis(fig[1, 1], aspect = DataAspect()) hidedecorations!(ax) - xlims!(-2.5, 2.5) + xlims!(-2.7, 2.4) ylims!(-2.5, 2.5) lines!( - ax, ps .+ Point2f(-1.2, -1.2), linewidth = 20, miter_limit = 1.0, color = :black, + ax, ps .+ Point2f(-1.2, -1.2), linewidth = 20, miter_limit = 51pi/180, color = :black, jointstyle = :round ) lines!( - ax, ps .+ Point2f(+1.2, -1.2), linewidth = 20, miter_limit = 2.0, color = :black, + ax, ps .+ Point2f(+1.2, -1.2), linewidth = 20, miter_limit = 129pi/180, color = :black, jointstyle = :bevel ) - lines!(ax, ps .+ Point2f(-1.2, +1.2), linewidth = 20, miter_limit = 1.0, color = :black) - lines!(ax, ps .+ Point2f(+1.2, +1.2), linewidth = 20, miter_limit = 2.0, color = :black) + lines!(ax, ps .+ Point2f(-1.2, +1.2), linewidth = 20, miter_limit = 51pi/180, color = :black) + lines!(ax, ps .+ Point2f(+1.2, +1.2), linewidth = 20, miter_limit = 129pi/180, color = :black) fig end From 61ce6a913f084d5d28a132c9abf2e3e41c13ef5d Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 10 Apr 2024 12:51:56 +0200 Subject: [PATCH 23/37] note change in default miter_limit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c24ef94f4..7c2eeaf940e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -- Added `linecap` and `jointstyle` attributes for lines and linesegments [#3771](https://github.com/MakieOrg/Makie.jl/pull/3771) +- Added `linecap` and `jointstyle` attributes for lines and linesegments. Also normalized `miter_limit` to 60° across all backends. [#3771](https://github.com/MakieOrg/Makie.jl/pull/3771) ## [0.21.0] - 2024-03-0X From 95260882bd60b39344309fa28a68d26f23aef680 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 10 Apr 2024 20:26:43 +0200 Subject: [PATCH 24/37] add new attributes to recipes --- MakieCore/src/basic_plots.jl | 11 +++++++---- src/basic_recipes/bracket.jl | 6 +++++- src/basic_recipes/contours.jl | 9 +++++++++ src/basic_recipes/error_and_rangebars.jl | 10 +++++++--- src/basic_recipes/poly.jl | 7 +++++-- src/basic_recipes/scatterlines.jl | 6 ++++++ src/basic_recipes/series.jl | 13 +++++++++---- src/basic_recipes/streamplot.jl | 6 ++++++ src/basic_recipes/triplot.jl | 8 ++++++-- src/theming.jl | 3 +++ 10 files changed, 63 insertions(+), 16 deletions(-) diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index db49b9c3eb2..1c0f2df7a03 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -310,11 +310,11 @@ Creates a connected line plot for each element in `(x, y, z)`, `(x, y)` or `posi "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" linestyle = nothing "Sets the type of linecap used, i.e. :butt (flat with no extrusion), :square (flat with 1 linewidth extrusion) or :round." - linecap = :butt + linecap = @inherit linecap "Controls whether line joints are rounded (:round) or not (:miter)." - jointstyle = :miter + jointstyle = @inherit jointstyle "Sets the minimum inner joint angle below which miter joints truncate. See also `Makie.miter_distance_to_angle()`" - miter_limit = pi/3 + miter_limit = @inherit miter_limit "Sets which attributes to cycle when creating multiple plots." cycle = [:color] mixin_generic_plot_attributes()... @@ -352,7 +352,7 @@ $(Base.Docs.doc(MakieCore.generic_plot_attributes!)) "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" linestyle = nothing "Sets the type of linecap used, i.e. :butt (flat with no extrusion), :square (flat with 1 linewidth extrusion) or :round." - linecap = :butt + linecap = @inherit linecap "Sets which attributes to cycle when creating multiple plots." cycle = [:color] mixin_generic_plot_attributes()... @@ -589,6 +589,9 @@ Plots polygons, which are defined by strokewidth = @inherit patchstrokewidth "Sets the pattern of the line (e.g. `:solid`, `:dot`, `:dashdot`)" linestyle = nothing + linecap = @inherit linecap + jointstyle = @inherit jointstyle + miter_limit = @inherit miter_limit shading = NoShading diff --git a/src/basic_recipes/bracket.jl b/src/basic_recipes/bracket.jl index 18d96e4e6df..7ad1796e382 100644 --- a/src/basic_recipes/bracket.jl +++ b/src/basic_recipes/bracket.jl @@ -28,6 +28,9 @@ By default each label is rotated parallel to the line between the bracket points textcolor = @inherit textcolor linewidth = @inherit linewidth linestyle = :solid + linecap = @inherit linecap + jointstyle = @inherit jointstyle + miter_limit = @inherit miter_limit justification = automatic style = :curly end @@ -110,7 +113,8 @@ function plot!(pl::Bracket) # Avoid scale!() / translate!() / rotate!() to affect these series!(pl, bp; space = :pixel, solid_color = pl.color, linewidth = pl.linewidth, - linestyle = pl.linestyle, transformation = Transformation()) + linestyle = pl.linestyle, linecap = pl.linecap, jointstyle = pl.jointstyle, + miter_limit = pl.miter_limit, transformation = Transformation()) text!(pl, text_tuples, space = :pixel, align = pl.align, offset = textoffset_vec, fontsize = pl.fontsize, font = pl.font, rotation = autorotations, color = pl.textcolor, justification = pl.justification, model = Mat4f(I)) diff --git a/src/basic_recipes/contours.jl b/src/basic_recipes/contours.jl index e02e43806f7..2d7c845bd4d 100644 --- a/src/basic_recipes/contours.jl +++ b/src/basic_recipes/contours.jl @@ -25,6 +25,9 @@ If only `z::Matrix` is supplied, the indices of the elements in `z` will be used levels = 5 linewidth = 1.0 linestyle = nothing + linecap = @inherit linecap + jointstyle = @inherit jointstyle + miter_limit = @inherit miter_limit enable_depth = true """ If `true`, adds text labels to the contour lines. @@ -160,6 +163,9 @@ function plot!(plot::Contour{<: Tuple{X, Y, Z, Vol}}) where {X, Y, Z, Vol} pop!(attr, :color) pop!(attr, :linestyle) pop!(attr, :linewidth) + pop!(attr, :linecap) + pop!(attr, :jointstyle) + pop!(attr, :miter_limit) volume!(plot, attr, x, y, z, volume) end @@ -326,6 +332,9 @@ function plot!(plot::T) where T <: Union{Contour, Contour3d} color = colors, linewidth = plot.linewidth, linestyle = plot.linestyle, + linecap = plot.linecap, + jointstyle = plot.jointstyle, + miter_limit = plot.miter_limit, visible=plot.visible, transparency=plot.transparency, overdraw=plot.overdraw, diff --git a/src/basic_recipes/error_and_rangebars.jl b/src/basic_recipes/error_and_rangebars.jl index 8d0cb954a1c..2cc6711a5fb 100644 --- a/src/basic_recipes/error_and_rangebars.jl +++ b/src/basic_recipes/error_and_rangebars.jl @@ -21,6 +21,7 @@ If you want to plot intervals from low to high values instead of relative errors color = @inherit linecolor "The thickness of the lines in screen units." linewidth = @inherit linewidth + linecap = @inherit linecap "The direction in which the bars are drawn. Can be `:x` or `:y`." direction = :y cycle = [:color] @@ -46,6 +47,7 @@ If you want to plot errors relative to a reference value, use `errorbars`. color = @inherit linecolor "The thickness of the lines in screen units." linewidth = @inherit linewidth + linecap = @inherit linecap "The direction in which the bars are drawn. Can be `:x` or `:y`." direction = :y cycle = [:color] @@ -194,7 +196,9 @@ function _plot_bars!(plot, linesegpairs, is_in_y_direction) f_if(condition, f, arg) = condition ? f(arg) : arg - @extract plot (whiskerwidth, color, linewidth, visible, colormap, colorscale, colorrange, inspectable, transparency) + @extract plot ( + whiskerwidth, color, linewidth, linecap, visible, colormap, colorscale, colorrange, + inspectable, transparency) scene = parent_scene(plot) @@ -231,12 +235,12 @@ function _plot_bars!(plot, linesegpairs, is_in_y_direction) end linesegments!( - plot, linesegpairs, color = color, linewidth = linewidth, visible = visible, + plot, linesegpairs, color = color, linewidth = linewidth, linecap = linecap, visible = visible, colormap = colormap, colorscale = colorscale, colorrange = colorrange, inspectable = inspectable, transparency = transparency ) linesegments!( - plot, whiskers, color = whiskercolors, linewidth = whiskerlinewidths, + plot, whiskers, color = whiskercolors, linewidth = whiskerlinewidths, linecap = linecap, visible = visible, colormap = colormap, colorscale = colorscale, colorrange = colorrange, inspectable = inspectable, transparency = transparency, space = :pixel, model = Mat4f(I) # overwrite scale!() / translate!() / rotate!() diff --git a/src/basic_recipes/poly.jl b/src/basic_recipes/poly.jl index 71faaebc21e..de8a7cab78d 100644 --- a/src/basic_recipes/poly.jl +++ b/src/basic_recipes/poly.jl @@ -37,7 +37,8 @@ function plot!(plot::Poly{<: Tuple{Union{GeometryBasics.Mesh, GeometryPrimitive} wireframe!( plot, plot[1], color = plot[:strokecolor], linestyle = plot[:linestyle], space = plot[:space], - linewidth = plot[:strokewidth], visible = plot[:visible], overdraw = plot[:overdraw], + linewidth = plot[:strokewidth], linecap = plot[:linecap], + visible = plot[:visible], overdraw = plot[:overdraw], inspectable = plot[:inspectable], transparency = plot[:transparency], colormap = plot[:strokecolormap] ) @@ -167,7 +168,9 @@ function plot!(plot::Poly{<: Tuple{<: Union{Polygon, AbstractVector{<: PolyEleme plot, outline, visible = plot.visible, color = stroke, linestyle = plot.linestyle, alpha = plot.alpha, colormap = plot.strokecolormap, - linewidth = plot.strokewidth, space = plot.space, + linewidth = plot.strokewidth, linecap = plot.linecap, + jointstyle = plot.jointstyle, miter_limit = plot.miter_limit, + space = plot.space, overdraw = plot.overdraw, transparency = plot.transparency, inspectable = plot.inspectable, depth_shift = -1f-5 ) diff --git a/src/basic_recipes/scatterlines.jl b/src/basic_recipes/scatterlines.jl index 286c9ef5644..f4a7f353757 100644 --- a/src/basic_recipes/scatterlines.jl +++ b/src/basic_recipes/scatterlines.jl @@ -10,6 +10,9 @@ Plots `scatter` markers and `lines` between them. linestyle = nothing "Sets the width of the line in screen units" linewidth = @inherit linewidth + linecap = @inherit linecap + jointstyle = @inherit jointstyle + miter_limit = @inherit miter_limit markercolor = automatic markercolormap = automatic markercolorrange = automatic @@ -55,6 +58,9 @@ function plot!(p::Plot{scatterlines, <:NTuple{N, Any}}) where N color = p.color, linestyle = p.linestyle, linewidth = p.linewidth, + linecap = p.linecap, + jointstyle = p.jointstyle, + miter_limit = p.miter_limit, colormap = p.colormap, colorscale = p.colorscale, colorrange = p.colorrange, diff --git a/src/basic_recipes/series.jl b/src/basic_recipes/series.jl index d65bdc8779f..12839a0218f 100644 --- a/src/basic_recipes/series.jl +++ b/src/basic_recipes/series.jl @@ -17,6 +17,9 @@ If any of `marker`, `markersize`, `markercolor`, `strokecolor` or `strokewidth` solid_color=nothing labels=nothing linestyle=:solid + linecap = @inherit linecap + jointstyle = @inherit jointstyle + miter_limit = @inherit miter_limit marker=nothing markersize=nothing markercolor=automatic @@ -58,7 +61,7 @@ function convert_arguments(::Type{<: Series}, arg::AbstractVector{<: AbstractVec end function plot!(plot::Series) - @extract plot (curves, labels, linewidth, color, solid_color, space, linestyle) + @extract plot (curves, labels, linewidth, linecap, jointstyle, miter_limit, color, solid_color, space, linestyle) sargs = [:marker, :markersize, :strokecolor, :strokewidth] scatter = Dict((f => plot[f] for f in sargs if !isnothing(plot[f][]))) nseries = length(curves[]) @@ -79,11 +82,13 @@ function plot!(plot::Series) mcolor = plot.markercolor markercolor = lift((mc, sc)-> mc == automatic ? sc : mc, plot, mcolor, series_color) scatterlines!(plot, positions; - linewidth=linewidth, color=series_color, markercolor=series_color, + linewidth=linewidth, linecap = plot.linecap, jointstyle = jointstyle, + miter_limit = miter_limit, color=series_color, markercolor=series_color, label=label[], scatter..., space = space, linestyle = series_linestyle) else - lines!(plot, positions; linewidth=linewidth, color=series_color, label=label, space = space, - linestyle = series_linestyle) + lines!(plot, positions; linewidth=linewidth, linecap = plot.linecap, + jointstyle = jointstyle, miter_limit = miter_limit, color=series_color, + label=label, space = space, linestyle = series_linestyle) end end end diff --git a/src/basic_recipes/streamplot.jl b/src/basic_recipes/streamplot.jl index 58bf3fb90e7..33eadcbbca1 100644 --- a/src/basic_recipes/streamplot.jl +++ b/src/basic_recipes/streamplot.jl @@ -31,6 +31,9 @@ See the function `Makie.streamplot_impl` for implementation details. quality = 16 linewidth = @inherit linewidth + linecap = @inherit linecap + jointstyle = @inherit jointstyle + miter_limit = @inherit miter_limit linestyle = nothing MakieCore.mixin_colormap_attributes()... MakieCore.mixin_generic_plot_attributes()... @@ -178,6 +181,9 @@ function plot!(p::StreamPlot) lift(x->x[3], p, data), color = lift(last, p, data), linestyle = p.linestyle, + linecap = p.linecap, + jointstyle = p.jointstyle, + miter_limit = p.miter_limit, linewidth = p.linewidth; colormap_args..., generic_plot_attributes... diff --git a/src/basic_recipes/triplot.jl b/src/basic_recipes/triplot.jl index 151d447bef4..821518625eb 100644 --- a/src/basic_recipes/triplot.jl +++ b/src/basic_recipes/triplot.jl @@ -33,6 +33,9 @@ Plots a triangulation based on the provided position or `Triangulation` from Del linestyle=:solid "Sets the color of the triangles." triangle_color= :transparent + linecap = @inherit linecap + jointstyle = @inherit jointstyle + miter_limit = @inherit miter_limit # Convex hull settings "Sets the color of the convex hull." @@ -222,11 +225,12 @@ function Makie.plot!(p::Triplot{<:Tuple{<:DelTri.Triangulation}}) poly!(p, points_2f, triangles_3f; strokewidth=p.strokewidth, strokecolor=p.strokecolor, color=p.triangle_color) linesegments!(p, ghost_edges_2f; color=p.ghost_edge_color, linewidth=p.ghost_edge_linewidth, - linestyle=p.ghost_edge_linestyle, xautolimits=false, yautolimits=false) + linecap=p.linecap, linestyle=p.ghost_edge_linestyle, xautolimits=false, yautolimits=false) lines!(p, convex_hull_2f; color=p.convex_hull_color, linewidth=p.convex_hull_linewidth, + linecap = p.linecap, jointstyle = p.jointstyle, miter_limit = p.miter_limit, linestyle=p.convex_hull_linestyle, depth_shift=-1.0f-5) linesegments!(p, constrained_edges_2f; color=p.constrained_edge_color, depth_shift=-2.0f-5, - linewidth=p.constrained_edge_linewidth, linestyle=p.constrained_edge_linestyle) + linecap=p.linecap, linewidth=p.constrained_edge_linewidth, linestyle=p.constrained_edge_linestyle) scatter!(p, present_points_2f; markersize=p.markersize, color=p.markercolor, strokecolor=p.strokecolor, marker=p.marker, visible=p.show_points, depth_shift=-3.0f-5) return p diff --git a/src/theming.jl b/src/theming.jl index 5eb76a75567..26596d5d134 100644 --- a/src/theming.jl +++ b/src/theming.jl @@ -48,6 +48,9 @@ const MAKIE_DEFAULT_THEME = Attributes( linecolor = :black, linewidth = 1.5, linestyle = nothing, + linecap = :butt, + jointstyle = :miter, + miter_limit = pi/3, patchcolor = RGBAf(0, 0, 0, 0.6), patchstrokecolor = :black, patchstrokewidth = 0, From 4fde44152efeb2ea9eedb6cd60bc928d2ab78e5a Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 10 Apr 2024 23:43:46 +0200 Subject: [PATCH 25/37] rename jointstyle -> joinstyle --- CairoMakie/src/primitives.jl | 6 +++--- GLMakie/assets/shader/lines.geom | 12 ++++++------ MakieCore/src/basic_plots.jl | 4 ++-- ReferenceTests/src/tests/primitives.jl | 8 ++++---- WGLMakie/src/Lines.js | 12 ++++++------ WGLMakie/src/lines.jl | 4 ++-- WGLMakie/src/wglmakie.bundled.js | 12 ++++++------ docs/reference/plots/lines.md | 2 +- src/basic_recipes/bracket.jl | 4 ++-- src/basic_recipes/contours.jl | 6 +++--- src/basic_recipes/poly.jl | 2 +- src/basic_recipes/scatterlines.jl | 4 ++-- src/basic_recipes/series.jl | 8 ++++---- src/basic_recipes/streamplot.jl | 4 ++-- src/basic_recipes/triplot.jl | 4 ++-- src/conversions.jl | 2 +- src/theming.jl | 2 +- 17 files changed, 48 insertions(+), 48 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index bdbe8e98570..7d7dea9a14d 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -138,10 +138,10 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio miter_angle = to_value(get(primitive, :miter_limit, 2pi/3)) set_miter_limit(ctx, 2.0 * Makie.miter_angle_to_distance(miter_angle)) - jointstyle = to_value(get(primitive, :jointstyle, :miter)) - if jointstyle == :round + joinstyle = to_value(get(primitive, :joinstyle, :miter)) + if joinstyle == :round Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_ROUND) - elseif jointstyle == :bevel + elseif joinstyle == :bevel Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_BEVEL) else # :miter Cairo.set_line_join(ctx, Cairo.CAIRO_LINE_JOIN_MITER) diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 64164e6a308..cab417319fa 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -43,7 +43,7 @@ uniform float pattern_length; uniform vec2 resolution; uniform int linecap; -uniform int jointstyle; +uniform int joinstyle; uniform float miter_limit; // Constants @@ -298,12 +298,12 @@ void main(void) vec2 miter_n1 = miter.x < -0.4 ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); vec2 miter_n2 = miter.y < -0.4 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); - // Are we truncating the joint based on miter limit or jointstyle? + // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow // miter joints a when v1 ≈ v2 (v0) bvec2 is_truncated = bvec2( - (jointstyle == BEVEL) ? miter.x < 0.99 : miter.x < miter_limit, - (jointstyle == BEVEL) ? miter.y < 0.99 : miter.y < miter_limit + (joinstyle == BEVEL) ? miter.x < 0.99 : miter.x < miter_limit, + (joinstyle == BEVEL) ? miter.y < 0.99 : miter.y < miter_limit ); // miter vectors (line vector matching miter normal) @@ -403,8 +403,8 @@ void main(void) // 0 :butt/normal cap or joint | 1 :square cap | 2 rounded cap/joint f_capmode = ivec2( - isvalid[0] ? jointstyle : linecap, - isvalid[3] ? jointstyle : linecap + isvalid[0] ? joinstyle : linecap, + isvalid[3] ? joinstyle : linecap ); // Generate interpolated/varying outputs: diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index 1c0f2df7a03..ec2339f1232 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -312,7 +312,7 @@ Creates a connected line plot for each element in `(x, y, z)`, `(x, y)` or `posi "Sets the type of linecap used, i.e. :butt (flat with no extrusion), :square (flat with 1 linewidth extrusion) or :round." linecap = @inherit linecap "Controls whether line joints are rounded (:round) or not (:miter)." - jointstyle = @inherit jointstyle + joinstyle = @inherit joinstyle "Sets the minimum inner joint angle below which miter joints truncate. See also `Makie.miter_distance_to_angle()`" miter_limit = @inherit miter_limit "Sets which attributes to cycle when creating multiple plots." @@ -590,7 +590,7 @@ Plots polygons, which are defined by "Sets the pattern of the line (e.g. `:solid`, `:dot`, `:dashdot`)" linestyle = nothing linecap = @inherit linecap - jointstyle = @inherit jointstyle + joinstyle = @inherit joinstyle miter_limit = @inherit miter_limit shading = NoShading diff --git a/ReferenceTests/src/tests/primitives.jl b/ReferenceTests/src/tests/primitives.jl index c709db8f0be..70e882a0931 100644 --- a/ReferenceTests/src/tests/primitives.jl +++ b/ReferenceTests/src/tests/primitives.jl @@ -58,7 +58,7 @@ end scene end -@reference_test "Linecaps and jointstyles" begin +@reference_test "Linecaps and joinstyles" begin fig = Figure(size = (550, 450)) ps = [Point2f(-2, 0), Point2f(0), Point2f(2, 0)] r = 2.0 @@ -76,7 +76,7 @@ end p = lines!( ax, ps, linewidth = 20, linecap = (:butt, :square, :round)[i], - jointstyle = (:miter, :bevel, :round)[j] + joinstyle = (:miter, :bevel, :round)[j] ) scatterlines!(ax, ps, color = :orange) end @@ -100,11 +100,11 @@ begin ylims!(-2.5, 2.5) lines!( ax, ps .+ Point2f(-1.2, -1.2), linewidth = 20, miter_limit = 51pi/180, color = :black, - jointstyle = :round + joinstyle = :round ) lines!( ax, ps .+ Point2f(+1.2, -1.2), linewidth = 20, miter_limit = 129pi/180, color = :black, - jointstyle = :bevel + joinstyle = :bevel ) lines!(ax, ps .+ Point2f(-1.2, +1.2), linewidth = 20, miter_limit = 51pi/180, color = :black) lines!(ax, ps .+ Point2f(+1.2, +1.2), linewidth = 20, miter_limit = 129pi/180, color = :black) diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index 5836c7c9419..e24bb0364c6 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -453,12 +453,12 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 miter_n1 = miter.x < -0.0 ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); vec2 miter_n2 = miter.y < -0.0 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); - // Are we truncating the joint based on miter limit or jointstyle? + // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow // miter joints a when v1 ≈ v2 (v0) bool[2] is_truncated = bool[2]( - (int(jointstyle) == BEVEL) ? miter.x < 0.99 : miter.x < miter_limit, - (int(jointstyle) == BEVEL) ? miter.y < 0.99 : miter.y < miter_limit + (int(joinstyle) == BEVEL) ? miter.x < 0.99 : miter.x < miter_limit, + (int(joinstyle) == BEVEL) ? miter.y < 0.99 : miter.y < miter_limit ); // miter vectors (line vector matching miter normal) @@ -561,10 +561,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { f_cumulative_length = lastlen_start; - // linecap + jointstyle + // linecap + joinstyle f_capmode = ivec2( - isvalid[0] ? jointstyle : linecap, - isvalid[3] ? jointstyle : linecap + isvalid[0] ? joinstyle : linecap, + isvalid[3] ? joinstyle : linecap ); diff --git a/WGLMakie/src/lines.jl b/WGLMakie/src/lines.jl index 5ef1db2fbf1..f56e60b77c7 100644 --- a/WGLMakie/src/lines.jl +++ b/WGLMakie/src/lines.jl @@ -1,5 +1,5 @@ function serialize_three(scene::Scene, plot::Union{Lines, LineSegments}) - Makie.@converted_attribute plot (linewidth, linestyle, linecap, jointstyle) + Makie.@converted_attribute plot (linewidth, linestyle, linecap, joinstyle) uniforms = Dict( :model => map(Makie.patch_model, f32_conversion_obs(plot), plot.model), @@ -8,7 +8,7 @@ function serialize_three(scene::Scene, plot::Union{Lines, LineSegments}) :linecap => linecap, ) if plot isa Lines - uniforms[:jointstyle] = jointstyle + uniforms[:joinstyle] = joinstyle uniforms[:miter_limit] = map(x -> cos(pi - x), plot, plot.miter_limit) end diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index ddf5a1282b5..db06258188e 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21716,12 +21716,12 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 miter_n1 = miter.x < -0.0 ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); vec2 miter_n2 = miter.y < -0.0 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); - // Are we truncating the joint based on miter limit or jointstyle? + // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow // miter joints a when v1 ≈ v2 (v0) bool[2] is_truncated = bool[2]( - (int(jointstyle) == BEVEL) ? miter.x < 0.99 : miter.x < miter_limit, - (int(jointstyle) == BEVEL) ? miter.y < 0.99 : miter.y < miter_limit + (int(joinstyle) == BEVEL) ? miter.x < 0.99 : miter.x < miter_limit, + (int(joinstyle) == BEVEL) ? miter.y < 0.99 : miter.y < miter_limit ); // miter vectors (line vector matching miter normal) @@ -21824,10 +21824,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { f_cumulative_length = lastlen_start; - // linecap + jointstyle + // linecap + joinstyle f_capmode = ivec2( - isvalid[0] ? jointstyle : linecap, - isvalid[3] ? jointstyle : linecap + isvalid[0] ? joinstyle : linecap, + isvalid[3] ? joinstyle : linecap ); diff --git a/docs/reference/plots/lines.md b/docs/reference/plots/lines.md index 5806587450c..0bf0ab98b33 100644 --- a/docs/reference/plots/lines.md +++ b/docs/reference/plots/lines.md @@ -68,7 +68,7 @@ for i in 1:3, j in 1:3 lines!( ps .+ Point2f(i, -j), linewidth = 20, linecap = (:butt, :square, :round)[i], - jointstyle = (:miter, :bevel, :round)[j] + joinstyle = (:miter, :bevel, :round)[j] ) scatterlines!(ps .+ Point2f(i, -j), color = :gray) end diff --git a/src/basic_recipes/bracket.jl b/src/basic_recipes/bracket.jl index 7ad1796e382..2222170e0f9 100644 --- a/src/basic_recipes/bracket.jl +++ b/src/basic_recipes/bracket.jl @@ -29,7 +29,7 @@ By default each label is rotated parallel to the line between the bracket points linewidth = @inherit linewidth linestyle = :solid linecap = @inherit linecap - jointstyle = @inherit jointstyle + joinstyle = @inherit joinstyle miter_limit = @inherit miter_limit justification = automatic style = :curly @@ -113,7 +113,7 @@ function plot!(pl::Bracket) # Avoid scale!() / translate!() / rotate!() to affect these series!(pl, bp; space = :pixel, solid_color = pl.color, linewidth = pl.linewidth, - linestyle = pl.linestyle, linecap = pl.linecap, jointstyle = pl.jointstyle, + linestyle = pl.linestyle, linecap = pl.linecap, joinstyle = pl.joinstyle, miter_limit = pl.miter_limit, transformation = Transformation()) text!(pl, text_tuples, space = :pixel, align = pl.align, offset = textoffset_vec, fontsize = pl.fontsize, font = pl.font, rotation = autorotations, color = pl.textcolor, diff --git a/src/basic_recipes/contours.jl b/src/basic_recipes/contours.jl index 2d7c845bd4d..f33bd3c9656 100644 --- a/src/basic_recipes/contours.jl +++ b/src/basic_recipes/contours.jl @@ -26,7 +26,7 @@ If only `z::Matrix` is supplied, the indices of the elements in `z` will be used linewidth = 1.0 linestyle = nothing linecap = @inherit linecap - jointstyle = @inherit jointstyle + joinstyle = @inherit joinstyle miter_limit = @inherit miter_limit enable_depth = true """ @@ -164,7 +164,7 @@ function plot!(plot::Contour{<: Tuple{X, Y, Z, Vol}}) where {X, Y, Z, Vol} pop!(attr, :linestyle) pop!(attr, :linewidth) pop!(attr, :linecap) - pop!(attr, :jointstyle) + pop!(attr, :joinstyle) pop!(attr, :miter_limit) volume!(plot, attr, x, y, z, volume) end @@ -333,7 +333,7 @@ function plot!(plot::T) where T <: Union{Contour, Contour3d} linewidth = plot.linewidth, linestyle = plot.linestyle, linecap = plot.linecap, - jointstyle = plot.jointstyle, + joinstyle = plot.joinstyle, miter_limit = plot.miter_limit, visible=plot.visible, transparency=plot.transparency, diff --git a/src/basic_recipes/poly.jl b/src/basic_recipes/poly.jl index de8a7cab78d..5462f4f9fa2 100644 --- a/src/basic_recipes/poly.jl +++ b/src/basic_recipes/poly.jl @@ -169,7 +169,7 @@ function plot!(plot::Poly{<: Tuple{<: Union{Polygon, AbstractVector{<: PolyEleme color = stroke, linestyle = plot.linestyle, alpha = plot.alpha, colormap = plot.strokecolormap, linewidth = plot.strokewidth, linecap = plot.linecap, - jointstyle = plot.jointstyle, miter_limit = plot.miter_limit, + joinstyle = plot.joinstyle, miter_limit = plot.miter_limit, space = plot.space, overdraw = plot.overdraw, transparency = plot.transparency, inspectable = plot.inspectable, depth_shift = -1f-5 diff --git a/src/basic_recipes/scatterlines.jl b/src/basic_recipes/scatterlines.jl index f4a7f353757..116ab13c96f 100644 --- a/src/basic_recipes/scatterlines.jl +++ b/src/basic_recipes/scatterlines.jl @@ -11,7 +11,7 @@ Plots `scatter` markers and `lines` between them. "Sets the width of the line in screen units" linewidth = @inherit linewidth linecap = @inherit linecap - jointstyle = @inherit jointstyle + joinstyle = @inherit joinstyle miter_limit = @inherit miter_limit markercolor = automatic markercolormap = automatic @@ -59,7 +59,7 @@ function plot!(p::Plot{scatterlines, <:NTuple{N, Any}}) where N linestyle = p.linestyle, linewidth = p.linewidth, linecap = p.linecap, - jointstyle = p.jointstyle, + joinstyle = p.joinstyle, miter_limit = p.miter_limit, colormap = p.colormap, colorscale = p.colorscale, diff --git a/src/basic_recipes/series.jl b/src/basic_recipes/series.jl index 12839a0218f..cbd7fe51eee 100644 --- a/src/basic_recipes/series.jl +++ b/src/basic_recipes/series.jl @@ -18,7 +18,7 @@ If any of `marker`, `markersize`, `markercolor`, `strokecolor` or `strokewidth` labels=nothing linestyle=:solid linecap = @inherit linecap - jointstyle = @inherit jointstyle + joinstyle = @inherit joinstyle miter_limit = @inherit miter_limit marker=nothing markersize=nothing @@ -61,7 +61,7 @@ function convert_arguments(::Type{<: Series}, arg::AbstractVector{<: AbstractVec end function plot!(plot::Series) - @extract plot (curves, labels, linewidth, linecap, jointstyle, miter_limit, color, solid_color, space, linestyle) + @extract plot (curves, labels, linewidth, linecap, joinstyle, miter_limit, color, solid_color, space, linestyle) sargs = [:marker, :markersize, :strokecolor, :strokewidth] scatter = Dict((f => plot[f] for f in sargs if !isnothing(plot[f][]))) nseries = length(curves[]) @@ -82,12 +82,12 @@ function plot!(plot::Series) mcolor = plot.markercolor markercolor = lift((mc, sc)-> mc == automatic ? sc : mc, plot, mcolor, series_color) scatterlines!(plot, positions; - linewidth=linewidth, linecap = plot.linecap, jointstyle = jointstyle, + linewidth=linewidth, linecap = plot.linecap, joinstyle = joinstyle, miter_limit = miter_limit, color=series_color, markercolor=series_color, label=label[], scatter..., space = space, linestyle = series_linestyle) else lines!(plot, positions; linewidth=linewidth, linecap = plot.linecap, - jointstyle = jointstyle, miter_limit = miter_limit, color=series_color, + joinstyle = joinstyle, miter_limit = miter_limit, color=series_color, label=label, space = space, linestyle = series_linestyle) end end diff --git a/src/basic_recipes/streamplot.jl b/src/basic_recipes/streamplot.jl index 33eadcbbca1..909eaf030e8 100644 --- a/src/basic_recipes/streamplot.jl +++ b/src/basic_recipes/streamplot.jl @@ -32,7 +32,7 @@ See the function `Makie.streamplot_impl` for implementation details. linewidth = @inherit linewidth linecap = @inherit linecap - jointstyle = @inherit jointstyle + joinstyle = @inherit joinstyle miter_limit = @inherit miter_limit linestyle = nothing MakieCore.mixin_colormap_attributes()... @@ -182,7 +182,7 @@ function plot!(p::StreamPlot) color = lift(last, p, data), linestyle = p.linestyle, linecap = p.linecap, - jointstyle = p.jointstyle, + joinstyle = p.joinstyle, miter_limit = p.miter_limit, linewidth = p.linewidth; colormap_args..., diff --git a/src/basic_recipes/triplot.jl b/src/basic_recipes/triplot.jl index 821518625eb..d83247f4a3e 100644 --- a/src/basic_recipes/triplot.jl +++ b/src/basic_recipes/triplot.jl @@ -34,7 +34,7 @@ Plots a triangulation based on the provided position or `Triangulation` from Del "Sets the color of the triangles." triangle_color= :transparent linecap = @inherit linecap - jointstyle = @inherit jointstyle + joinstyle = @inherit joinstyle miter_limit = @inherit miter_limit # Convex hull settings @@ -227,7 +227,7 @@ function Makie.plot!(p::Triplot{<:Tuple{<:DelTri.Triangulation}}) linesegments!(p, ghost_edges_2f; color=p.ghost_edge_color, linewidth=p.ghost_edge_linewidth, linecap=p.linecap, linestyle=p.ghost_edge_linestyle, xautolimits=false, yautolimits=false) lines!(p, convex_hull_2f; color=p.convex_hull_color, linewidth=p.convex_hull_linewidth, - linecap = p.linecap, jointstyle = p.jointstyle, miter_limit = p.miter_limit, + linecap = p.linecap, joinstyle = p.joinstyle, miter_limit = p.miter_limit, linestyle=p.convex_hull_linestyle, depth_shift=-1.0f-5) linesegments!(p, constrained_edges_2f; color=p.constrained_edge_color, depth_shift=-2.0f-5, linecap=p.linecap, linewidth=p.constrained_edge_linewidth, linestyle=p.constrained_edge_linestyle) diff --git a/src/conversions.jl b/src/conversions.jl index f323ed0e196..380a357bfc4 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -1080,7 +1080,7 @@ function convert_attribute(value::Symbol, ::key"linecap") end end -function convert_attribute(value::Symbol, ::key"jointstyle") +function convert_attribute(value::Symbol, ::key"joinstyle") # TODO: make this an enum? # 0 and 2 are shared between this and linecap. 1 has no equivalent here vals = Dict(:miter => 0, :round => 2, :bevel => 3) diff --git a/src/theming.jl b/src/theming.jl index 26596d5d134..ca02c4d2472 100644 --- a/src/theming.jl +++ b/src/theming.jl @@ -49,7 +49,7 @@ const MAKIE_DEFAULT_THEME = Attributes( linewidth = 1.5, linestyle = nothing, linecap = :butt, - jointstyle = :miter, + joinstyle = :miter, miter_limit = pi/3, patchcolor = RGBAf(0, 0, 0, 0.6), patchstrokecolor = :black, From 922eff3b28fde9385aa5e88ad968a7565e1b86c8 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 10 Apr 2024 23:56:31 +0200 Subject: [PATCH 26/37] update a few more jointstyles --- CHANGELOG.md | 2 +- CairoMakie/src/primitives.jl | 2 +- docs/reference/plots/lines.md | 2 +- src/conversions.jl | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c2eeaf940e..31fdf3796e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -- Added `linecap` and `jointstyle` attributes for lines and linesegments. Also normalized `miter_limit` to 60° across all backends. [#3771](https://github.com/MakieOrg/Makie.jl/pull/3771) +- Added `linecap` and `joinstyle` attributes for lines and linesegments. Also normalized `miter_limit` to 60° across all backends. [#3771](https://github.com/MakieOrg/Makie.jl/pull/3771) ## [0.21.0] - 2024-03-0X diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 7d7dea9a14d..cd7c7352220 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -134,7 +134,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio Cairo.set_line_cap(ctx, Cairo.CAIRO_LINE_CAP_BUTT) end - # joint style + # joinstyle miter_angle = to_value(get(primitive, :miter_limit, 2pi/3)) set_miter_limit(ctx, 2.0 * Makie.miter_angle_to_distance(miter_angle)) diff --git a/docs/reference/plots/lines.md b/docs/reference/plots/lines.md index 0bf0ab98b33..829dee470af 100644 --- a/docs/reference/plots/lines.md +++ b/docs/reference/plots/lines.md @@ -52,7 +52,7 @@ f ``` \end{examplefigure} -### Linecaps and Jointstyles +### Linecaps and Joinstyles \begin{examplefigure}{svg = true} ```julia diff --git a/src/conversions.jl b/src/conversions.jl index 380a357bfc4..e57abe42615 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -1085,7 +1085,7 @@ function convert_attribute(value::Symbol, ::key"joinstyle") # 0 and 2 are shared between this and linecap. 1 has no equivalent here vals = Dict(:miter => 0, :round => 2, :bevel => 3) return get(vals, value) do - error("$value is not a valid joint style. It must be one of $(keys(vals)).") + error("$value is not a valid joinstyle. It must be one of $(keys(vals)).") end end @@ -1097,7 +1097,7 @@ distance between the line point (corner at linewidth → 0) and the outer corner The distance is given in units of linewidth. If `directed = true` the distance in line direction is used instead. -With respect to miter limits, a line joint gets truncated if the corner distance +With respect to miter limits, a linejoin gets truncated if the corner distance exceeds a given limit or analogously the angle falls below a certain limit. This function calculates the angle given a distance. """ From 1c07498a9f8d7b3db99362cf0fca6000f4f13f1a Mon Sep 17 00:00:00 2001 From: Frederic Freyer Date: Thu, 25 Apr 2024 11:46:58 +0200 Subject: [PATCH 27/37] Fix rare missing/duplicate pixels in truncated joint (#3794) * rework truncated joint discard * update WGLMakie * fix non-solid linestyle joints --- GLMakie/assets/shader/line_segment.geom | 18 ++- GLMakie/assets/shader/lines.frag | 72 +++++----- GLMakie/assets/shader/lines.geom | 76 +++++----- GLMakie/src/drawing_primitives.jl | 6 + WGLMakie/src/Lines.js | 184 +++++++++++------------- WGLMakie/src/lines.jl | 1 + WGLMakie/src/wglmakie.bundled.js | 128 ++++++++--------- 7 files changed, 237 insertions(+), 248 deletions(-) diff --git a/GLMakie/assets/shader/line_segment.geom b/GLMakie/assets/shader/line_segment.geom index d2d38444477..fd32a3508bd 100644 --- a/GLMakie/assets/shader/line_segment.geom +++ b/GLMakie/assets/shader/line_segment.geom @@ -17,9 +17,7 @@ in {{stripped_color_type}} g_color[]; in uvec2 g_id[]; in float g_thickness[]; -out float f_quad_sdf0; -out vec3 f_quad_sdf1; -out float f_quad_sdf2; +out vec3 f_quad_sdf; out vec2 f_truncation; out float f_linestart; out float f_linelength; @@ -28,12 +26,13 @@ flat out float f_linewidth; flat out vec4 f_pattern_overwrite; flat out uvec2 f_id; flat out vec2 f_extrusion; -flat out vec2 f_discard_limit; flat out {{stripped_color_type}} f_color1; flat out {{stripped_color_type}} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; flat out ivec2 f_capmode; +flat out vec4 f_linepoints; +flat out vec4 f_miter_vecs; const float AA_RADIUS = 0.8; const float AA_THICKNESS = 2.0 * AA_RADIUS; @@ -81,12 +80,11 @@ void main(void) vec2 n1 = normal_vector(v1); // Set invalid / ignored outputs - f_quad_sdf0 = 1e12; // no joint to previous segment - f_quad_sdf2 = 1e12; // not joint to next segment f_truncation = vec2(-1e12); // no truncated joint f_pattern_overwrite = vec4(-1e12, 1.0, 1e12, 1.0); // no joints to overwrite f_extrusion = vec2(0.5); // no joints needing extrusion - f_discard_limit = vec2(10.0); // no joints needing discards + f_linepoints = vec4(-1e12); + f_miter_vecs = vec4(-1); // constants f_color1 = g_color[0]; @@ -123,9 +121,9 @@ void main(void) vec2 VP2 = position.xy - p2.xy; // sdf of this segment - f_quad_sdf1.x = dot(VP1, -v1.xy); - f_quad_sdf1.y = dot(VP2, v1.xy); - f_quad_sdf1.z = n_offset; + f_quad_sdf.x = dot(VP1, -v1.xy); + f_quad_sdf.y = dot(VP2, v1.xy); + f_quad_sdf.z = n_offset; // finalize vertex EmitVertex(); diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index 316a9d44be2..dee98e53bbb 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -11,9 +11,7 @@ struct Nothing{ //Nothing type, to encode if some variable doesn't contain any d bool _; //empty structs are not allowed }; -in highp float f_quad_sdf0; -in highp vec3 f_quad_sdf1; -in highp float f_quad_sdf2; +in highp vec3 f_quad_sdf; in vec2 f_truncation; in float f_linestart; in float f_linelength; @@ -21,13 +19,14 @@ in float f_linelength; flat in float f_linewidth; // half the real linewidth here because we count from center flat in vec4 f_pattern_overwrite; flat in vec2 f_extrusion; -flat in vec2 f_discard_limit; flat in {{stripped_color_type}} f_color1; flat in {{stripped_color_type}} f_color2; flat in float f_alpha_weight; flat in uvec2 f_id; flat in float f_cumulative_length; flat in ivec2 f_capmode; +flat in vec4 f_linepoints; +flat in vec4 f_miter_vecs; {{pattern_type}} pattern; uniform float pattern_length; @@ -142,38 +141,38 @@ void write2framebuffer(vec4 color, uvec2 id); void main(){ vec4 color; - // f_quad_sdf1.x is the negative distance from p1 in v1 direction + // f_quad_sdf.x is the negative distance from p1 in v1 direction // (where f_cumulative_length applies) so we need to subtract here vec2 uv = vec2( - (f_cumulative_length - f_quad_sdf1.x + 0.5) / (2.0 * f_linewidth * pattern_length), - 0.5 + 0.5 * f_quad_sdf1.z / f_linewidth + (f_cumulative_length - f_quad_sdf.x + 0.5) / (2.0 * f_linewidth * pattern_length), + 0.5 + 0.5 * f_quad_sdf.z / f_linewidth ); // #ifndef DEBUG if (!debug) { - // discard fragments that are "more inside" the other segment to remove - // overlap between adjacent line segments. (truncated joints) - float dist_in_prev = max(f_quad_sdf0, - f_discard_limit.x); - float dist_in_next = max(f_quad_sdf2, - f_discard_limit.y); - if (dist_in_prev < f_quad_sdf1.x || dist_in_next < f_quad_sdf1.y) + // discard fragments that are other side of the truncated joint + float discard_sdf1 = dot(gl_FragCoord.xy - f_linepoints.xy, f_miter_vecs.xy); + float discard_sdf2 = dot(gl_FragCoord.xy - f_linepoints.zw, f_miter_vecs.zw); + if ((f_quad_sdf.x > 0.0 && discard_sdf1 > 0.0) || + (f_quad_sdf.y > 0.0 && discard_sdf2 >= 0.0)) discard; float sdf; - // f_quad_sdf1.x includes everything from p1 in p2-p1 direction, i.e. > - // f_quad_sdf2.y includes everything from p2 in p1-p2 direction, i.e. < + // f_quad_sdf.x includes everything from p1 in p2-p1 direction, i.e. > + // f_quad_sdf.y includes everything from p2 in p1-p2 direction, i.e. < // < < | > < > < | > > // < < 1->----<->----<-2 > > // < < | > < > < | > > if (f_capmode.x == ROUND) { // in circle(p1, halfwidth) || is beyond p1 in p2-p1 direction - sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); + sdf = min(sqrt(f_quad_sdf.x * f_quad_sdf.x + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.x); } else if (f_capmode.x == SQUARE) { // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) - sdf = f_quad_sdf1.x - f_linewidth; + sdf = f_quad_sdf.x - f_linewidth; } else { // miter or bevel joint or :butt cap // variable shift in -(p2-p1) direction to make space for joints - sdf = f_quad_sdf1.x - f_extrusion.x; + sdf = f_quad_sdf.x - f_extrusion.x; // do truncate joints sdf = max(sdf, f_truncation.x); } @@ -181,12 +180,12 @@ if (!debug) { // Same as above but for p2 if (f_capmode.y == ROUND) { // rounded joint or cap sdf = max(sdf, - min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) + min(sqrt(f_quad_sdf.y * f_quad_sdf.y + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.y) ); } else if (f_capmode.y == SQUARE) { // :square cap - sdf = max(sdf, f_quad_sdf1.y - f_linewidth); + sdf = max(sdf, f_quad_sdf.y - f_linewidth); } else { // miter or bevel joint or :butt cap - sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); + sdf = max(sdf, f_quad_sdf.y - f_extrusion.y); sdf = max(sdf, f_truncation.y); } @@ -195,15 +194,15 @@ if (!debug) { // ^ | ^ ^ | ^ // 1------------2 // ^ | ^ ^ | ^ - sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); + sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); // inner truncation (AA for overlapping parts) // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) // and b is the (smoothly) cut of part just after discard triggers (i.e not visible) - // 100.0x sdf makes the sdf much more sharp, avoiding overdraw in the center - sdf = max(sdf, min(f_quad_sdf1.x + 1.0, 100.0 * (f_quad_sdf1.x - f_quad_sdf0) - 1.0)); - sdf = max(sdf, min(f_quad_sdf1.y + 1.0, 100.0 * (f_quad_sdf1.y - f_quad_sdf2) - 1.0)); + // 100.0x sdf makes the sdf much more sharply, avoiding overdraw in the center + sdf = max(sdf, min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0)); + sdf = max(sdf, min(f_quad_sdf.y + 1.0, 100.0 * discard_sdf2 - 1.0)); // pattern application sdf = max(sdf, get_pattern_sdf(pattern, uv)); @@ -216,11 +215,11 @@ if (!debug) { // p1 v1 // '. ---> // '---------- - // -f_quad_sdf1.x is the distance from p1, positive in v1 direction + // -f_quad_sdf.x is the distance from p1, positive in v1 direction // f_linestart is the distance between p1 and the left edge along v1 direction // f_start_length.y is the distance between the edges of this segment, in v1 direction // so this is 0 at the left edge and 1 at the right edge (with extrusion considered) - float factor = (-f_quad_sdf1.x - f_linestart) / f_linelength; + float factor = (-f_quad_sdf.x - f_linestart) / f_linelength; color = get_color(f_color1 + factor * (f_color2 - f_color1), color_map, color_norm); color.a *= f_alpha_weight; @@ -239,32 +238,31 @@ if (!debug) { color.rgb += (2 * mod(f_id.y, 2) - 1) * 0.1; // mark "outside" define by quad_sdf in black - float sdf = max(f_quad_sdf1.x - f_extrusion.x, f_quad_sdf1.y - f_extrusion.y); - sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); + float sdf = max(f_quad_sdf.x - f_extrusion.x, f_quad_sdf.y - f_extrusion.y); + sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); color.rgb -= vec3(0.4) * step(0.0, sdf); // Mark discarded space in red/blue - float dist_in_prev = max(f_quad_sdf0, - f_discard_limit.x); - float dist_in_next = max(f_quad_sdf2, - f_discard_limit.y); - if (dist_in_prev < f_quad_sdf1.x) + float discard_sdf1 = dot(gl_FragCoord.xy - f_linepoints.xy, f_miter_vecs.xy); + float discard_sdf2 = dot(gl_FragCoord.xy - f_linepoints.zw, f_miter_vecs.zw); + if (f_quad_sdf.x > 0.0 && discard_sdf1 > 0.0) color.r += 0.5; - if (dist_in_next <= f_quad_sdf1.y) { + if (f_quad_sdf.y > 0.0 && discard_sdf2 >= 0.0) color.b += 0.5; - } // remaining overlap as softer red/blue - if (f_quad_sdf1.x - f_quad_sdf0 - 1.0 > 0.0) + if (discard_sdf1 - 1.0 > 0.0) color.r += 0.2; - if (f_quad_sdf1.y - f_quad_sdf2 - 1.0 > 0.0) + if (discard_sdf2 - 1.0 > 0.0) color.b += 0.2; // Mark regions excluded via truncation in green color.g += 0.5 * step(0.0, max(f_truncation.x, f_truncation.y)); // and inner truncation as softer green - if (min(f_quad_sdf1.x + 1.0, 100.0 * (f_quad_sdf1.x - f_quad_sdf0) - 1.0) > 0.0) + if (min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0) > 0.0) color.g += 0.2; - if (min(f_quad_sdf1.y + 1.0, 100.0 * (f_quad_sdf1.y - f_quad_sdf2) - 1.0) > 0.0) + if (min(f_quad_sdf.y + 1.0, 100.0 * discard_sdf2 - 1.0) > 0.0) color.g += 0.2; // mark pattern in white diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index cab417319fa..d7b3e9082db 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -17,9 +17,7 @@ in uvec2 g_id[]; in int g_valid_vertex[]; in float g_thickness[]; -out highp float f_quad_sdf0; -out highp vec3 f_quad_sdf1; -out highp float f_quad_sdf2; +out highp vec3 f_quad_sdf; out vec2 f_truncation; out float f_linestart; out float f_linelength; @@ -34,6 +32,8 @@ flat out {{stripped_color_type}} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; flat out ivec2 f_capmode; +flat out vec4 f_linepoints; +flat out vec4 f_miter_vecs; out vec3 o_view_pos; out vec3 o_view_normal; @@ -41,6 +41,7 @@ out vec3 o_view_normal; {{pattern_type}} pattern; uniform float pattern_length; uniform vec2 resolution; +uniform vec2 scene_origin; uniform int linecap; uniform int joinstyle; @@ -64,9 +65,7 @@ struct LineVertex { vec3 position; int index; - float quad_sdf0; - vec3 quad_sdf1; - float quad_sdf2; + vec3 quad_sdf; vec2 truncation; float linestart; @@ -75,9 +74,7 @@ struct LineVertex { void emit_vertex(LineVertex vertex) { gl_Position = vec4((2.0 * vertex.position.xy / resolution) - 1.0, vertex.position.z, 1.0); - f_quad_sdf0 = vertex.quad_sdf0; - f_quad_sdf1 = vertex.quad_sdf1; - f_quad_sdf2 = vertex.quad_sdf2; + f_quad_sdf = vertex.quad_sdf; f_truncation = vertex.truncation; f_linestart = vertex.linestart; f_linelength = vertex.linelength; @@ -295,8 +292,8 @@ void main(void) // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); - vec2 miter_n1 = miter.x < -0.4 ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = miter.y < -0.4 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); + vec2 miter_n1 = miter.x < -0.4 ? sign(dot(v0.xy, n1)) * normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < -0.4 ? sign(dot(v1.xy, n2)) * normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow @@ -370,14 +367,35 @@ void main(void) if (adjustment[0] != 0.0 || adjustment[1] != 0.0) shape_factor = vec2(1.0); - // For truncated miter joints we discard overlapping sections of - // the two involved line segments. To avoid discarding far into - // the line segment we limit the range here. (Without this short - // segments can cut holes into longer sections.) - f_discard_limit = vec2( - is_truncated[0] ? 0.0 : 1e12, - is_truncated[1] ? 0.0 : 1e12 - ); + // For truncated miter joints we discard overlapping sections of the two + // involved line segments. To identify which sections overlap we calculate + // the signed distance in +- miter vector direction from the shared line + // point in fragment shader. We pass the necessary data here. If we do not + // have a truncated joint we adjust the data here to never discard. + // Why not calculate the sdf here? + // If we calculate the sdf here and pass it as an interpolated vertex output + // the values we get between the two line segments will differ since the + // the vertices each segment interpolates from differ. This causes the + // discard check to rarely be true or false for both segments, resulting in + // duplicated or missing pixel/fragment draw. + // Passing the line point and miter vector instead should fix this issue, + // because both of these values come from the same calculation between the + // two segments. I.e. (previous segment).p2 == (next segment).p1 and + // (previous segment).miter_v2 == (next segment).miter_v1 should be the case. + if (isvalid[0] && is_truncated[0] && (adjustment[0] == 0.0)) { + f_linepoints.xy = p1.xy + scene_origin; // FragCoords are relative to the window + f_miter_vecs.xy = -miter_v1.xy; // but p1/p2 is relative to the scene origin + } else { + f_linepoints.xy = vec2(-1e12); // FragCoord > 0 + f_miter_vecs.xy = normalize(vec2(-1)); + } + if (isvalid[3] && is_truncated[1] && (adjustment[1] == 0.0)) { + f_linepoints.zw = p2.xy + scene_origin; + f_miter_vecs.zw = miter_v2.xy; + } else { + f_linepoints.zw = vec2(-1e12); + f_miter_vecs.zw = normalize(vec2(-1)); + } // used to elongate sdf to include joints // if start/end elongate slightly so that there is no AA gap in loops @@ -447,24 +465,10 @@ void main(void) vec2 VP1 = vertex.position.xy - p1.xy; vec2 VP2 = vertex.position.xy - p2.xy; - // Signed distance of the previous segment from the shared point - // p1 in line direction. Used decide which segments renders - // which joint fragment/pixel for truncated joints. - if (isvalid[0] && (adjustment[0] == 0) && is_truncated[0]) - vertex.quad_sdf0 = dot(VP1, v0.xy); - else - vertex.quad_sdf0 = 1e12; - // sdf of this segment - vertex.quad_sdf1.x = dot(VP1, -v1.xy); - vertex.quad_sdf1.y = dot(VP2, v1.xy); - vertex.quad_sdf1.z = dot(VP1, n1); - - // SDF for next segment, see quad_sdf0 - if (isvalid[3] && (adjustment[1] == 0) && is_truncated[1]) - vertex.quad_sdf2 = dot(VP2, -v2.xy); - else - vertex.quad_sdf2 = 1e12; + vertex.quad_sdf.x = dot(VP1, -v1.xy); + vertex.quad_sdf.y = dot(VP2, v1.xy); + vertex.quad_sdf.z = dot(VP1, n1); // sdf for creating a flat cap on truncated joints // (sign(dot(...)) detects if line bends left or right) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index d51d3c7ad25..29e19a74e7e 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -447,6 +447,9 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(plot::Lines)) data = Dict{Symbol, Any}(gl_attributes) data[:miter_limit] = map(x -> Float32(cos(pi - x)), plot, miter_limit) positions = handle_view(plot[1], data) + data[:scene_origin] = map(plot, data[:px_per_unit], scene.viewport) do ppu, viewport + Vec2f(ppu * origin(viewport)) + end space = plot.space if isnothing(to_value(linestyle)) @@ -485,6 +488,9 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(plot::LineSegme return cached_robj!(screen, scene, plot) do gl_attributes data = Dict{Symbol, Any}(gl_attributes) data[:pattern] = pop!(data, :linestyle) + data[:scene_origin] = map(plot, data[:px_per_unit], scene.viewport) do ppu, viewport + Vec2f(ppu * origin(viewport)) + end positions = handle_view(plot[1], data) # positions = lift(apply_transform, plot, transform_func_obs(plot), positions, plot.space) diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index e24bb0364c6..6aeca9ddad4 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -39,28 +39,13 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { /// Linessegments //////////////////////////////////////////////////////////////////////// - // Note: - // If we run into problems with the number of varying vertex attributes - // used here, try splitting up f_quad_sdf1. The vec3 might be taking - // a full vec4 slot, while vec2 + float would not. - // Alternatively: - // - f_truncation could probably be traded for a float f_truncation_distance, - // with truncation happening at something like - // trunc_sdf = f_quad_sdf1.x - f_quad_sdf0 +- f_truncation_distance - // - f_quad_sdf1.x and .y could potentially be merged used abs like the - // normal direction (f_quad_sdf1.z). - // If those are not possible without degrading line quality we could - // also generate a simplified fragment shader for linesegments which - // drops all the unnecessary attributes to enable that as a workaround. - return `precision mediump int; precision highp float; ${attribute_decl} - out highp float f_quad_sdf0; // invalid / not needed - out highp vec3 f_quad_sdf1; - out highp float f_quad_sdf2; // invalid / not needed + + out vec3 f_quad_sdf; out vec2 f_truncation; // invalid / not needed out float f_linestart; // constant out float f_linelength; @@ -68,13 +53,14 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out vec2 f_extrusion; // invalid / not needed flat out float f_linewidth; flat out vec4 f_pattern_overwrite; // invalid / not needed - flat out vec2 f_discard_limit; // invalid / not needed flat out uint f_instance_id; flat out ${color} f_color1; flat out ${color} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; flat out ivec2 f_capmode; + flat out vec4 f_linepoints; // invalid / not needed + flat out vec4 f_miter_vecs; // invalid / not needed ${uniform_decl} @@ -152,9 +138,6 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // invalid - no joints requiring pattern adjustments f_pattern_overwrite = vec4(-1e12, 1.0, 1e12, 1.0); - // invalid - no joints that need pixels discarded - f_discard_limit = vec2(10.0); - // invalid - no joints requiring line sdfs to be extruded f_extrusion = vec2(0.0); @@ -166,6 +149,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // we restart patterns for each segment f_cumulative_length = 0.0; + // no joints means these should be set to a "never discard" state + f_linepoints = vec4(-1e12); + f_miter_vecs = vec4(-1); + //////////////////////////////////////////////////////////////////// // Varying vertex data @@ -184,16 +171,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 VP1 = point.xy - p1.xy; vec2 VP2 = point.xy - p2.xy; - // invalid - no joint to compute overlap with - f_quad_sdf0 = 1e12; - // sdf of this segment - f_quad_sdf1.x = dot(VP1, -v1.xy); - f_quad_sdf1.y = dot(VP2, v1.xy); - f_quad_sdf1.z = dot(VP1, n1); - - // invalid - no joint to compute overlap with - f_quad_sdf2 = 1e12; + f_quad_sdf.x = dot(VP1, -v1.xy); + f_quad_sdf.y = dot(VP2, v1.xy); + f_quad_sdf.z = dot(VP1, n1); // invalid - no joint to truncate f_truncation = vec2(-1e12); @@ -222,9 +203,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { ${attribute_decl} - out highp float f_quad_sdf0; - out highp vec3 f_quad_sdf1; - out highp float f_quad_sdf2; + out vec3 f_quad_sdf; out vec2 f_truncation; out float f_linestart; out float f_linelength; @@ -232,13 +211,14 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out vec2 f_extrusion; flat out float f_linewidth; flat out vec4 f_pattern_overwrite; - flat out vec2 f_discard_limit; flat out uint f_instance_id; flat out ${color} f_color1; flat out ${color} f_color2; flat out float f_alpha_weight; flat out float f_cumulative_length; flat out ivec2 f_capmode; + flat out vec4 f_linepoints; + flat out vec4 f_miter_vecs; ${uniform_decl} @@ -450,8 +430,8 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); - vec2 miter_n1 = miter.x < -0.0 ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = miter.y < -0.0 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); + vec2 miter_n1 = miter.x < -0.0 ? sign(dot(v0.xy, n1)) *normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < -0.0 ? sign(dot(v1.xy, n2)) *normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow @@ -535,15 +515,35 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // Static vertex data //////////////////////////////////////////////////////////////////// - - // For truncated miter joints we discard overlapping sections of - // the two involved line segments. To avoid discarding far into - // the line segment we limit the range here. (Without this short - // segments can cut holes into longer sections.) - f_discard_limit = vec2( - is_truncated[0] ? 0.0 : 1e12, - is_truncated[1] ? 0.0 : 1e12 - ); + // For truncated miter joints we discard overlapping sections of the two + // involved line segments. To identify which sections overlap we calculate + // the signed distance in +- miter vector direction from the shared line + // point in fragment shader. We pass the necessary data here. If we do not + // have a truncated joint we adjust the data here to never discard. + // Why not calculate the sdf here? + // If we calculate the sdf here and pass it as an interpolated vertex output + // the values we get between the two line segments will differ since the + // the vertices each segment interpolates from differ. This causes the + // discard check to rarely be true or false for both segments, resulting in + // duplicated or missing pixel/fragment draw. + // Passing the line point and miter vector instead should fix this issue, + // because both of these values come from the same calculation between the + // two segments. I.e. (previous segment).p2 == (next segment).p1 and + // (previous segment).miter_v2 == (next segment).miter_v1 should be the case. + if (isvalid[0] && is_truncated[0] && (adjustment[0] == 0.0)) { + f_linepoints.xy = p1.xy + px_per_unit * scene_origin; // FragCoords are relative to the window + f_miter_vecs.xy = -miter_v1.xy; // but p1/p2 is relative to the scene origin + } else { + f_linepoints.xy = vec2(-1e12); // FragCoord > 0 + f_miter_vecs.xy = normalize(vec2(-1)); + } + if (isvalid[3] && is_truncated[1] && (adjustment[1] == 0.0)) { + f_linepoints.zw = p2.xy + px_per_unit * scene_origin; + f_miter_vecs.zw = miter_v2.xy; + } else { + f_linepoints.zw = vec2(-1e12); + f_miter_vecs.zw = normalize(vec2(-1)); + } // Used to elongate sdf to include joints // if start/end elongate slightly so that there is no AA gap in loops @@ -604,24 +604,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 VP1 = point.xy - p1.xy; vec2 VP2 = point.xy - p2.xy; - // Signed distance of the previous segment from the shared point - // p1 in line direction. Used decide which segments renders - // which joint fragment/pixel for truncated joints. - if (isvalid[0] && (adjustment[0] == 0.0) && is_truncated[0]) - f_quad_sdf0 = dot(VP1, v0.xy); - else - f_quad_sdf0 = 1e12; - // sdf of this segment - f_quad_sdf1.x = dot(VP1, -v1.xy); - f_quad_sdf1.y = dot(VP2, v1.xy); - f_quad_sdf1.z = dot(VP1, n1); - - // SDF for next segment, see quad_sdf0 - if (isvalid[3] && (adjustment[1] == 0.0) && is_truncated[1]) - f_quad_sdf2 = dot(VP2, -v2.xy); - else - f_quad_sdf2 = 1e12; + f_quad_sdf.x = dot(VP1, -v1.xy); + f_quad_sdf.y = dot(VP2, v1.xy); + f_quad_sdf.z = dot(VP1, n1); // sdf for creating a flat cap on truncated joints // (sign(dot(...)) detects if line bends left or right) @@ -672,9 +658,7 @@ function lines_fragment_shader(uniforms, attributes) { precision mediump sampler2D; precision mediump sampler3D; - in highp float f_quad_sdf0; - in highp vec3 f_quad_sdf1; - in highp float f_quad_sdf2; + in highp vec3 f_quad_sdf; in vec2 f_truncation; in float f_linestart; in float f_linelength; @@ -682,13 +666,14 @@ function lines_fragment_shader(uniforms, attributes) { flat in float f_linewidth; flat in vec4 f_pattern_overwrite; flat in vec2 f_extrusion; - flat in vec2 f_discard_limit; flat in ${color} f_color1; flat in ${color} f_color2; flat in float f_alpha_weight; flat in uint f_instance_id; flat in float f_cumulative_length; flat in ivec2 f_capmode; + flat in vec4 f_linepoints; + flat in vec4 f_miter_vecs; uniform uint object_id; ${uniform_decl} @@ -802,36 +787,36 @@ function lines_fragment_shader(uniforms, attributes) { void main(){ vec4 color; - // f_quad_sdf1.x is the distance from p1, negative in v1 direction. + // f_quad_sdf.x is the distance from p1, negative in v1 direction. vec2 uv = vec2( - (f_cumulative_length - f_quad_sdf1.x) / (2.0 * f_linewidth * pattern_length), - 0.5 + 0.5 * f_quad_sdf1.z / f_linewidth + (f_cumulative_length - f_quad_sdf.x) / (2.0 * f_linewidth * pattern_length), + 0.5 + 0.5 * f_quad_sdf.z / f_linewidth ); #ifndef DEBUG - // discard fragments that are "more inside" the other segment to remove - // overlap between adjacent line segments. (truncated joints) - float dist_in_prev = max(f_quad_sdf0, - f_discard_limit.x); - float dist_in_next = max(f_quad_sdf2, - f_discard_limit.y); - if (dist_in_prev < f_quad_sdf1.x || dist_in_next < f_quad_sdf1.y) + // discard fragments that are other side of the truncated joint + float discard_sdf1 = dot(gl_FragCoord.xy - f_linepoints.xy, f_miter_vecs.xy); + float discard_sdf2 = dot(gl_FragCoord.xy - f_linepoints.zw, f_miter_vecs.zw); + if ((f_quad_sdf.x > 0.0 && discard_sdf1 > 0.0) || + (f_quad_sdf.y > 0.0 && discard_sdf2 >= 0.0)) discard; float sdf; - // f_quad_sdf1.x includes everything from p1 in p2-p1 direction, i.e. > + // f_quad_sdf.x includes everything from p1 in p2-p1 direction, i.e. > // f_quad_sdf2.y includes everything from p2 in p1-p2 direction, i.e. < // < < | > < > < | > > // < < 1->----<->----<-2 > > // < < | > < > < | > > if (f_capmode.x == ROUND) { // in circle(p1, halfwidth) || is beyond p1 in p2-p1 direction - sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); + sdf = min(sqrt(f_quad_sdf.x * f_quad_sdf.x + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.x); } else if (f_capmode.x == SQUARE) { // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) - sdf = f_quad_sdf1.x - f_linewidth; + sdf = f_quad_sdf.x - f_linewidth; } else { // miter or bevel joint or :butt cap // variable shift in -(p2-p1) direction to make space for joints - sdf = f_quad_sdf1.x - f_extrusion.x; + sdf = f_quad_sdf.x - f_extrusion.x; // do truncate joints sdf = max(sdf, f_truncation.x); } @@ -839,12 +824,12 @@ function lines_fragment_shader(uniforms, attributes) { // Same as above but for p2 if (f_capmode.y == ROUND) { sdf = max(sdf, - min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) + min(sqrt(f_quad_sdf.y * f_quad_sdf.y + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.y) ); } else if (f_capmode.y == SQUARE) { - sdf = max(sdf, f_quad_sdf1.y - f_linewidth); + sdf = max(sdf, f_quad_sdf.y - f_linewidth); } else { // miter or bevel joint or :butt cap - sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); + sdf = max(sdf, f_quad_sdf.y - f_extrusion.y); sdf = max(sdf, f_truncation.y); } @@ -853,15 +838,15 @@ function lines_fragment_shader(uniforms, attributes) { // ^ | ^ ^ | ^ // 1------------2 // ^ | ^ ^ | ^ - sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); + sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); // inner truncation (AA for overlapping parts) // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) // and b is the (smoothly) cut of part where the discard triggers - // 100.0x sdf makes the sdf much more sharp, avoiding overdraw in the center - sdf = max(sdf, min(f_quad_sdf1.x + 1.0, 100.0 * (f_quad_sdf1.x - f_quad_sdf0) - 1.0)); - sdf = max(sdf, min(f_quad_sdf1.y + 1.0, 100.0 * (f_quad_sdf1.y - f_quad_sdf2) - 1.0)); + // 100.0x sdf makes the sdf much more sharply, avoiding overdraw in the center + sdf = max(sdf, min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0)); + sdf = max(sdf, min(f_quad_sdf.y + 1.0, 100.0 * discard_sdf2 - 1.0)); // pattern application sdf = max(sdf, get_pattern_sdf(pattern, uv)); @@ -874,11 +859,11 @@ function lines_fragment_shader(uniforms, attributes) { // p1 v1 // '. ---> // '---------- - // -f_quad_sdf1.x is the distance from p1, positive in v1 direction + // -f_quad_sdf.x is the distance from p1, positive in v1 direction // f_linestart is the distance between p1 and the left edge along v1 direction // f_start_length.y is the distance between the edges of this segment, in v1 direction // so this is 0 at the left edge and 1 at the right edge (with extrusion considered) - float factor = (-f_quad_sdf1.x - f_linestart) / f_linelength; + float factor = (-f_quad_sdf.x - f_linestart) / f_linelength; color = get_color(f_color1 + factor * (f_color2 - f_color1), colormap, colorrange); color.a *= aastep(0.0, -sdf) * f_alpha_weight; @@ -890,36 +875,35 @@ function lines_fragment_shader(uniforms, attributes) { color.rgb += (2.0 * mod(float(f_instance_id), 2.0) - 1.0) * 0.1; // show color interpolation as brightness gradient - // float factor = (-f_quad_sdf1.x - f_linestart) / f_linelength; + // float factor = (-f_quad_sdf.x - f_linestart) / f_linelength; // color.rgb += (2.0 * factor - 1.0) * 0.2; // mark "outside" define by quad_sdf in black - float sdf = max(f_quad_sdf1.x - f_extrusion.x, f_quad_sdf1.y - f_extrusion.y); - sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); + float sdf = max(f_quad_sdf.x - f_extrusion.x, f_quad_sdf.y - f_extrusion.y); + sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); color.rgb -= vec3(0.4) * step(0.0, sdf); // Mark discarded space in red/blue - float dist_in_prev = max(f_quad_sdf0, - f_discard_limit.x); - float dist_in_next = max(f_quad_sdf2, - f_discard_limit.y); - if (dist_in_prev < f_quad_sdf1.x) + float discard_sdf1 = dot(gl_FragCoord.xy - f_linepoints.xy, f_miter_vecs.xy); + float discard_sdf2 = dot(gl_FragCoord.xy - f_linepoints.zw, f_miter_vecs.zw); + if (f_quad_sdf.x > 0.0 && discard_sdf1 > 0.0) color.r += 0.5; - if (dist_in_next <= f_quad_sdf1.y) { + if (f_quad_sdf.y > 0.0 && discard_sdf2 >= 0.0) color.b += 0.5; - } // remaining overlap as softer red/blue - if (f_quad_sdf1.x - f_quad_sdf0 - 1.0 > 0.0) + if (discard_sdf1 - 1.0 > 0.0) color.r += 0.2; - if (f_quad_sdf1.y - f_quad_sdf2 - 1.0 > 0.0) + if (discard_sdf2 - 1.0 > 0.0) color.b += 0.2; // Mark regions excluded via truncation in green color.g += 0.5 * step(0.0, max(f_truncation.x, f_truncation.y)); - // and inner truncation as soft green - if (min(f_quad_sdf1.x + 1.0, 100.0 * (f_quad_sdf1.x - f_quad_sdf0) - 1.0) > 0.0) + // and inner truncation as softer green + if (min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0) > 0.0) color.g += 0.2; - if (min(f_quad_sdf1.y + 1.0, 100.0 * (f_quad_sdf1.y - f_quad_sdf2) - 1.0) > 0.0) + if (min(f_quad_sdf.y + 1.0, 100.0 * discard_sdf2 - 1.0) > 0.0) color.g += 0.2; // mark pattern in white diff --git a/WGLMakie/src/lines.jl b/WGLMakie/src/lines.jl index f56e60b77c7..f30d88a9a43 100644 --- a/WGLMakie/src/lines.jl +++ b/WGLMakie/src/lines.jl @@ -6,6 +6,7 @@ function serialize_three(scene::Scene, plot::Union{Lines, LineSegments}) :depth_shift => plot.depth_shift, :picking => false, :linecap => linecap, + :scene_origin => map(vp -> Vec2f(origin(vp)), plot, scene.viewport) ) if plot isa Lines uniforms[:joinstyle] = joinstyle diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index db06258188e..e212598f898 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -4858,7 +4858,7 @@ vec4 LinearTosRGB( in vec4 value ) { #else uniform sampler2D envMap; #endif - + #endif`, nm = `#ifdef USE_ENVMAP uniform float reflectivity; #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT ) @@ -4875,7 +4875,7 @@ vec4 LinearTosRGB( in vec4 value ) { #define ENV_WORLDPOS #endif #ifdef ENV_WORLDPOS - + varying vec3 vWorldPosition; #else varying vec3 vReflect; @@ -5686,7 +5686,7 @@ IncidentLight directLight; vec4 sampledDiffuseColor = texture2D( map, vMapUv ); #ifdef DECODE_VIDEO_TEXTURE sampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w ); - + #endif diffuseColor *= sampledDiffuseColor; #endif`, Pm = `#ifdef USE_MAP @@ -21336,7 +21336,6 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out vec2 f_extrusion; // invalid / not needed flat out float f_linewidth; flat out vec4 f_pattern_overwrite; // invalid / not needed - flat out vec2 f_discard_limit; // invalid / not needed flat out uint f_instance_id; flat out ${color} f_color1; flat out ${color} f_color2; @@ -21420,9 +21419,6 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // invalid - no joints requiring pattern adjustments f_pattern_overwrite = vec4(-1e12, 1.0, 1e12, 1.0); - // invalid - no joints that need pixels discarded - f_discard_limit = vec2(10.0); - // invalid - no joints requiring line sdfs to be extruded f_extrusion = vec2(0.0); @@ -21434,6 +21430,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // we restart patterns for each segment f_cumulative_length = 0.0; + // no joints means these should be set to a "never discard" state + f_linepoints = vec4(-1e12); + f_miter_vecs = vec4(-1); + //////////////////////////////////////////////////////////////////// // Varying vertex data @@ -21485,9 +21485,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { ${attribute_decl} - out highp float f_quad_sdf0; - out highp vec3 f_quad_sdf1; - out highp float f_quad_sdf2; + out vec3 f_quad_sdf; out vec2 f_truncation; out float f_linestart; out float f_linelength; @@ -21495,7 +21493,6 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out vec2 f_extrusion; flat out float f_linewidth; flat out vec4 f_pattern_overwrite; - flat out vec2 f_discard_limit; flat out uint f_instance_id; flat out ${color} f_color1; flat out ${color} f_color2; @@ -21798,15 +21795,35 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // Static vertex data //////////////////////////////////////////////////////////////////// - - // For truncated miter joints we discard overlapping sections of - // the two involved line segments. To avoid discarding far into - // the line segment we limit the range here. (Without this short - // segments can cut holes into longer sections.) - f_discard_limit = vec2( - is_truncated[0] ? 0.0 : 1e12, - is_truncated[1] ? 0.0 : 1e12 - ); + // For truncated miter joints we discard overlapping sections of the two + // involved line segments. To identify which sections overlap we calculate + // the signed distance in +- miter vector direction from the shared line + // point in fragment shader. We pass the necessary data here. If we do not + // have a truncated joint we adjust the data here to never discard. + // Why not calculate the sdf here? + // If we calculate the sdf here and pass it as an interpolated vertex output + // the values we get between the two line segments will differ since the + // the vertices each segment interpolates from differ. This causes the + // discard check to rarely be true or false for both segments, resulting in + // duplicated or missing pixel/fragment draw. + // Passing the line point and miter vector instead should fix this issue, + // because both of these values come from the same calculation between the + // two segments. I.e. (previous segment).p2 == (next segment).p1 and + // (previous segment).miter_v2 == (next segment).miter_v1 should be the case. + if (isvalid[0] && is_truncated[0] && (adjustment[0] == 0.0)) { + f_linepoints.xy = p1.xy + px_per_unit * scene_origin; // FragCoords are relative to the window + f_miter_vecs.xy = -miter_v1.xy; // but p1/p2 is relative to the scene origin + } else { + f_linepoints.xy = vec2(-1e12); // FragCoord > 0 + f_miter_vecs.xy = normalize(vec2(-1)); + } + if (isvalid[3] && is_truncated[1] && (adjustment[1] == 0.0)) { + f_linepoints.zw = p2.xy + px_per_unit * scene_origin; + f_miter_vecs.zw = miter_v2.xy; + } else { + f_linepoints.zw = vec2(-1e12); + f_miter_vecs.zw = normalize(vec2(-1)); + } // Used to elongate sdf to include joints // if start/end elongate slightly so that there is no AA gap in loops @@ -21867,24 +21884,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 VP1 = point.xy - p1.xy; vec2 VP2 = point.xy - p2.xy; - // Signed distance of the previous segment from the shared point - // p1 in line direction. Used decide which segments renders - // which joint fragment/pixel for truncated joints. - if (isvalid[0] && (adjustment[0] == 0.0) && is_truncated[0]) - f_quad_sdf0 = dot(VP1, v0.xy); - else - f_quad_sdf0 = 1e12; - // sdf of this segment - f_quad_sdf1.x = dot(VP1, -v1.xy); - f_quad_sdf1.y = dot(VP2, v1.xy); - f_quad_sdf1.z = dot(VP1, n1); - - // SDF for next segment, see quad_sdf0 - if (isvalid[3] && (adjustment[1] == 0.0) && is_truncated[1]) - f_quad_sdf2 = dot(VP2, -v2.xy); - else - f_quad_sdf2 = 1e12; + f_quad_sdf.x = dot(VP1, -v1.xy); + f_quad_sdf.y = dot(VP2, v1.xy); + f_quad_sdf.z = dot(VP1, n1); // sdf for creating a flat cap on truncated joints // (sign(dot(...)) detects if line bends left or right) @@ -21937,9 +21940,7 @@ function lines_fragment_shader(uniforms, attributes) { precision mediump sampler2D; precision mediump sampler3D; - in highp float f_quad_sdf0; - in highp vec3 f_quad_sdf1; - in highp float f_quad_sdf2; + in highp vec3 f_quad_sdf; in vec2 f_truncation; in float f_linestart; in float f_linelength; @@ -21947,7 +21948,6 @@ function lines_fragment_shader(uniforms, attributes) { flat in float f_linewidth; flat in vec4 f_pattern_overwrite; flat in vec2 f_extrusion; - flat in vec2 f_discard_limit; flat in ${color} f_color1; flat in ${color} f_color2; flat in float f_alpha_weight; @@ -22067,18 +22067,18 @@ function lines_fragment_shader(uniforms, attributes) { void main(){ vec4 color; - // f_quad_sdf1.x is the distance from p1, negative in v1 direction. + // f_quad_sdf.x is the distance from p1, negative in v1 direction. vec2 uv = vec2( - (f_cumulative_length - f_quad_sdf1.x) / (2.0 * f_linewidth * pattern_length), - 0.5 + 0.5 * f_quad_sdf1.z / f_linewidth + (f_cumulative_length - f_quad_sdf.x) / (2.0 * f_linewidth * pattern_length), + 0.5 + 0.5 * f_quad_sdf.z / f_linewidth ); #ifndef DEBUG - // discard fragments that are "more inside" the other segment to remove - // overlap between adjacent line segments. (truncated joints) - float dist_in_prev = max(f_quad_sdf0, - f_discard_limit.x); - float dist_in_next = max(f_quad_sdf2, - f_discard_limit.y); - if (dist_in_prev < f_quad_sdf1.x || dist_in_next < f_quad_sdf1.y) + // discard fragments that are other side of the truncated joint + float discard_sdf1 = dot(gl_FragCoord.xy - f_linepoints.xy, f_miter_vecs.xy); + float discard_sdf2 = dot(gl_FragCoord.xy - f_linepoints.zw, f_miter_vecs.zw); + if ((f_quad_sdf.x > 0.0 && discard_sdf1 > 0.0) || + (f_quad_sdf.y > 0.0 && discard_sdf2 >= 0.0)) discard; float sdf; @@ -22139,11 +22139,11 @@ function lines_fragment_shader(uniforms, attributes) { // p1 v1 // '. ---> // '---------- - // -f_quad_sdf1.x is the distance from p1, positive in v1 direction + // -f_quad_sdf.x is the distance from p1, positive in v1 direction // f_linestart is the distance between p1 and the left edge along v1 direction // f_start_length.y is the distance between the edges of this segment, in v1 direction // so this is 0 at the left edge and 1 at the right edge (with extrusion considered) - float factor = (-f_quad_sdf1.x - f_linestart) / f_linelength; + float factor = (-f_quad_sdf.x - f_linestart) / f_linelength; color = get_color(f_color1 + factor * (f_color2 - f_color1), colormap, colorrange); color.a *= aastep(0.0, -sdf) * f_alpha_weight; @@ -22155,36 +22155,35 @@ function lines_fragment_shader(uniforms, attributes) { color.rgb += (2.0 * mod(float(f_instance_id), 2.0) - 1.0) * 0.1; // show color interpolation as brightness gradient - // float factor = (-f_quad_sdf1.x - f_linestart) / f_linelength; + // float factor = (-f_quad_sdf.x - f_linestart) / f_linelength; // color.rgb += (2.0 * factor - 1.0) * 0.2; // mark "outside" define by quad_sdf in black - float sdf = max(f_quad_sdf1.x - f_extrusion.x, f_quad_sdf1.y - f_extrusion.y); - sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); + float sdf = max(f_quad_sdf.x - f_extrusion.x, f_quad_sdf.y - f_extrusion.y); + sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); color.rgb -= vec3(0.4) * step(0.0, sdf); // Mark discarded space in red/blue - float dist_in_prev = max(f_quad_sdf0, - f_discard_limit.x); - float dist_in_next = max(f_quad_sdf2, - f_discard_limit.y); - if (dist_in_prev < f_quad_sdf1.x) + float discard_sdf1 = dot(gl_FragCoord.xy - f_linepoints.xy, f_miter_vecs.xy); + float discard_sdf2 = dot(gl_FragCoord.xy - f_linepoints.zw, f_miter_vecs.zw); + if (f_quad_sdf.x > 0.0 && discard_sdf1 > 0.0) color.r += 0.5; - if (dist_in_next <= f_quad_sdf1.y) { + if (f_quad_sdf.y > 0.0 && discard_sdf2 >= 0.0) color.b += 0.5; - } // remaining overlap as softer red/blue - if (f_quad_sdf1.x - f_quad_sdf0 - 1.0 > 0.0) + if (discard_sdf1 - 1.0 > 0.0) color.r += 0.2; - if (f_quad_sdf1.y - f_quad_sdf2 - 1.0 > 0.0) + if (discard_sdf2 - 1.0 > 0.0) color.b += 0.2; // Mark regions excluded via truncation in green color.g += 0.5 * step(0.0, max(f_truncation.x, f_truncation.y)); - // and inner truncation as soft green - if (min(f_quad_sdf1.x + 1.0, 100.0 * (f_quad_sdf1.x - f_quad_sdf0) - 1.0) > 0.0) + // and inner truncation as softer green + if (min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0) > 0.0) color.g += 0.2; - if (min(f_quad_sdf1.y + 1.0, 100.0 * (f_quad_sdf1.y - f_quad_sdf2) - 1.0) > 0.0) + if (min(f_quad_sdf.y + 1.0, 100.0 * discard_sdf2 - 1.0) > 0.0) color.g += 0.2; // mark pattern in white @@ -23283,4 +23282,3 @@ export { pick_sorted as pick_sorted }; export { pick_native_uuid as pick_native_uuid }; export { pick_native_matrix as pick_native_matrix }; export { register_popup as register_popup }; - From 766cf6dcd5e74295398639f12e9b054676abc8ee Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 25 Apr 2024 13:21:43 +0200 Subject: [PATCH 28/37] improve truncated linecap a bit --- GLMakie/assets/shader/lines.geom | 6 ++++++ WGLMakie/src/Lines.js | 8 ++++++++ WGLMakie/src/wglmakie.bundled.js | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index d7b3e9082db..a49ddad13b2 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -352,6 +352,12 @@ void main(void) max(0.0, segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0][1] - extrusion[1][1]))) // +n ); + // Don't use shape_vector on line starts/ends to avoid cutting of linecaps. + // (This is irrelevant for :butt) + // TODO: consider elongating :butt-ed lines and adjusting rendering for + // :round-ed lines to get a linestart/end. + shape_factor = vec2(isvalid[0] ? shape_factor.x : 1.0, isvalid[3] ? shape_factor.y : 1.0); + // Generate static/flat outputs // If a pattern starts or stops drawing in a joint it will get diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index 6aeca9ddad4..b14582e1ebc 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -496,6 +496,14 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { ) ); + + // Don't use shape_vector on line starts/ends to avoid cutting of linecaps. + // (This is irrelevant for :butt) + // TODO: consider elongating :butt-ed lines and adjusting rendering for + // :round-ed lines to get a linestart/end. + shape_factor = isvalid[3 * int(is_end)] ? shape_factor : 1.0; + + // If a pattern starts or stops drawing in a joint it will get // fractured across the joint. To avoid this we either: // - adjust the involved line segments so that the patterns ends diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index e212598f898..816a0d2b9d5 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21776,6 +21776,14 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { ) ); + + // Don't use shape_vector on line starts/ends to avoid cutting of linecaps. + // (This is irrelevant for :butt) + // TODO: consider elongating :butt-ed lines and adjusting rendering for + // :round-ed lines to get a linestart/end. + shape_factor = isvalid[3 * int(is_end)] ? shape_factor : 1.0; + + // If a pattern starts or stops drawing in a joint it will get // fractured across the joint. To avoid this we either: // - adjust the involved line segments so that the patterns ends From b59c5082018d9f12e60115e05d1ba2b2550a0593 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 25 Apr 2024 13:36:06 +0200 Subject: [PATCH 29/37] regenerate wglmakie bundled --- WGLMakie/src/wglmakie.bundled.js | 56 ++++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 816a0d2b9d5..39e8991c797 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -4858,7 +4858,7 @@ vec4 LinearTosRGB( in vec4 value ) { #else uniform sampler2D envMap; #endif - + #endif`, nm = `#ifdef USE_ENVMAP uniform float reflectivity; #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT ) @@ -4875,7 +4875,7 @@ vec4 LinearTosRGB( in vec4 value ) { #define ENV_WORLDPOS #endif #ifdef ENV_WORLDPOS - + varying vec3 vWorldPosition; #else varying vec3 vReflect; @@ -5686,7 +5686,7 @@ IncidentLight directLight; vec4 sampledDiffuseColor = texture2D( map, vMapUv ); #ifdef DECODE_VIDEO_TEXTURE sampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w ); - + #endif diffuseColor *= sampledDiffuseColor; #endif`, Pm = `#ifdef USE_MAP @@ -21326,9 +21326,8 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { ${attribute_decl} - out highp float f_quad_sdf0; // invalid / not needed - out highp vec3 f_quad_sdf1; - out highp float f_quad_sdf2; // invalid / not needed + + out vec3 f_quad_sdf; out vec2 f_truncation; // invalid / not needed out float f_linestart; // constant out float f_linelength; @@ -21342,6 +21341,8 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out float f_alpha_weight; flat out float f_cumulative_length; flat out ivec2 f_capmode; + flat out vec4 f_linepoints; // invalid / not needed + flat out vec4 f_miter_vecs; // invalid / not needed ${uniform_decl} @@ -21452,16 +21453,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 VP1 = point.xy - p1.xy; vec2 VP2 = point.xy - p2.xy; - // invalid - no joint to compute overlap with - f_quad_sdf0 = 1e12; - // sdf of this segment - f_quad_sdf1.x = dot(VP1, -v1.xy); - f_quad_sdf1.y = dot(VP2, v1.xy); - f_quad_sdf1.z = dot(VP1, n1); - - // invalid - no joint to compute overlap with - f_quad_sdf2 = 1e12; + f_quad_sdf.x = dot(VP1, -v1.xy); + f_quad_sdf.y = dot(VP2, v1.xy); + f_quad_sdf.z = dot(VP1, n1); // invalid - no joint to truncate f_truncation = vec2(-1e12); @@ -21499,6 +21494,8 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { flat out float f_alpha_weight; flat out float f_cumulative_length; flat out ivec2 f_capmode; + flat out vec4 f_linepoints; + flat out vec4 f_miter_vecs; ${uniform_decl} @@ -21710,8 +21707,8 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); - vec2 miter_n1 = miter.x < -0.0 ? normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = miter.y < -0.0 ? normalize(v1.xy - v2.xy) : normalize(n1 + n2); + vec2 miter_n1 = miter.x < -0.0 ? sign(dot(v0.xy, n1)) *normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < -0.0 ? sign(dot(v1.xy, n2)) *normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow @@ -21962,6 +21959,8 @@ function lines_fragment_shader(uniforms, attributes) { flat in uint f_instance_id; flat in float f_cumulative_length; flat in ivec2 f_capmode; + flat in vec4 f_linepoints; + flat in vec4 f_miter_vecs; uniform uint object_id; ${uniform_decl} @@ -22091,20 +22090,20 @@ function lines_fragment_shader(uniforms, attributes) { float sdf; - // f_quad_sdf1.x includes everything from p1 in p2-p1 direction, i.e. > + // f_quad_sdf.x includes everything from p1 in p2-p1 direction, i.e. > // f_quad_sdf2.y includes everything from p2 in p1-p2 direction, i.e. < // < < | > < > < | > > // < < 1->----<->----<-2 > > // < < | > < > < | > > if (f_capmode.x == ROUND) { // in circle(p1, halfwidth) || is beyond p1 in p2-p1 direction - sdf = min(sqrt(f_quad_sdf1.x * f_quad_sdf1.x + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.x); + sdf = min(sqrt(f_quad_sdf.x * f_quad_sdf.x + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.x); } else if (f_capmode.x == SQUARE) { // everything in p2-p1 direction shifted by halfwidth in p1-p2 direction (i.e. include more) - sdf = f_quad_sdf1.x - f_linewidth; + sdf = f_quad_sdf.x - f_linewidth; } else { // miter or bevel joint or :butt cap // variable shift in -(p2-p1) direction to make space for joints - sdf = f_quad_sdf1.x - f_extrusion.x; + sdf = f_quad_sdf.x - f_extrusion.x; // do truncate joints sdf = max(sdf, f_truncation.x); } @@ -22112,12 +22111,12 @@ function lines_fragment_shader(uniforms, attributes) { // Same as above but for p2 if (f_capmode.y == ROUND) { sdf = max(sdf, - min(sqrt(f_quad_sdf1.y * f_quad_sdf1.y + f_quad_sdf1.z * f_quad_sdf1.z) - f_linewidth, f_quad_sdf1.y) + min(sqrt(f_quad_sdf.y * f_quad_sdf.y + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.y) ); } else if (f_capmode.y == SQUARE) { - sdf = max(sdf, f_quad_sdf1.y - f_linewidth); + sdf = max(sdf, f_quad_sdf.y - f_linewidth); } else { // miter or bevel joint or :butt cap - sdf = max(sdf, f_quad_sdf1.y - f_extrusion.y); + sdf = max(sdf, f_quad_sdf.y - f_extrusion.y); sdf = max(sdf, f_truncation.y); } @@ -22126,15 +22125,15 @@ function lines_fragment_shader(uniforms, attributes) { // ^ | ^ ^ | ^ // 1------------2 // ^ | ^ ^ | ^ - sdf = max(sdf, abs(f_quad_sdf1.z) - f_linewidth); + sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); // inner truncation (AA for overlapping parts) // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) // and b is the (smoothly) cut of part where the discard triggers - // 100.0x sdf makes the sdf much more sharp, avoiding overdraw in the center - sdf = max(sdf, min(f_quad_sdf1.x + 1.0, 100.0 * (f_quad_sdf1.x - f_quad_sdf0) - 1.0)); - sdf = max(sdf, min(f_quad_sdf1.y + 1.0, 100.0 * (f_quad_sdf1.y - f_quad_sdf2) - 1.0)); + // 100.0x sdf makes the sdf much more sharply, avoiding overdraw in the center + sdf = max(sdf, min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0)); + sdf = max(sdf, min(f_quad_sdf.y + 1.0, 100.0 * discard_sdf2 - 1.0)); // pattern application sdf = max(sdf, get_pattern_sdf(pattern, uv)); @@ -23290,3 +23289,4 @@ export { pick_sorted as pick_sorted }; export { pick_native_uuid as pick_native_uuid }; export { pick_native_matrix as pick_native_matrix }; export { register_popup as register_popup }; + From cfd91c2f60883fe0c44f85168165089c2af590c4 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 25 Apr 2024 14:31:03 +0200 Subject: [PATCH 30/37] tweak shape_factor --- GLMakie/assets/shader/lines.geom | 16 +- WGLMakie/src/Lines.js | 21 +- WGLMakie/src/wglmakie.js | 737 ------------------------------- 3 files changed, 12 insertions(+), 762 deletions(-) delete mode 100644 WGLMakie/src/wglmakie.js diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 7151507ff76..e0c510d58fc 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -347,16 +347,12 @@ void main(void) // '---' // To avoid drawing the "inverted" section we move the relevant // vertices to the crossing point (x) using this scaling factor. - vec2 shape_factor = vec2( - max(0.0, segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0][0] - extrusion[1][0]))), // -n - max(0.0, segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0][1] - extrusion[1][1]))) // +n - ); - - // Don't use shape_vector on line starts/ends to avoid cutting of linecaps. - // (This is irrelevant for :butt.) - // TODO: consider elongating :butt-ed lines and adjusting rendering for - // :round-ed lines to get a linestart/end. - shape_factor = vec2(isvalid[0] ? shape_factor.x : 1.0, isvalid[3] ? shape_factor.y : 1.0); + // TODO: skipping this for linestart/end avoid round and square being cut off + // but causes overlap... + vec2 shape_factor = (isvalid[0] && isvalid[3]) || (linecap == BUTT) ? vec2( + segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0][0] - extrusion[1][0])), // -n + segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0][1] - extrusion[1][1])) // +n + ) : vec2(1.0); // Generate static/flat outputs diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index b14582e1ebc..345a302c253 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -488,21 +488,12 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // '---' // To avoid drawing the "inverted" section we move the relevant // vertices to the crossing point (x) using this scaling factor. - float shape_factor = max( - 0.0, - segment_length / max( - segment_length, - (halfwidth + AA_THICKNESS) * (extrusion[0] - extrusion[1]) - ) - ); - - - // Don't use shape_vector on line starts/ends to avoid cutting of linecaps. - // (This is irrelevant for :butt) - // TODO: consider elongating :butt-ed lines and adjusting rendering for - // :round-ed lines to get a linestart/end. - shape_factor = isvalid[3 * int(is_end)] ? shape_factor : 1.0; - + // TODO: skipping this for linestart/end avoid round and square + // being cut off but causes overlap... + float shape_factor = 1.0; + if ((isvalid[0] && isvalid[3]) || (linecap == BUTT)) + shape_factor = segment_length / max(segment_length, + (halfwidth + AA_THICKNESS) * (extrusion[0] - extrusion[1])); // If a pattern starts or stops drawing in a joint it will get // fractured across the joint. To avoid this we either: diff --git a/WGLMakie/src/wglmakie.js b/WGLMakie/src/wglmakie.js deleted file mode 100644 index d023b735243..00000000000 --- a/WGLMakie/src/wglmakie.js +++ /dev/null @@ -1,737 +0,0 @@ -import * as THREE from "./THREE.js"; -import { getWebGLErrorMessage } from "./WEBGL.js"; -import { - delete_scenes, - insert_plot, - delete_plots, - delete_scene, - delete_three_scene, - find_plots, - deserialize_scene, - TEXTURE_ATLAS, - on_next_insert, - scene_cache, - plot_cache, - find_scene, -} from "./Serialization.js"; - -import { events2unitless } from "./Camera.js"; - -window.THREE = THREE; - -export function render_scene(scene, picking = false) { - const { camera, renderer, px_per_unit } = scene.screen; - const canvas = renderer.domElement; - if (!document.body.contains(canvas)) { - console.log("removing WGL context, canvas is not in the DOM anymore!"); - if (scene.screen.texture_atlas) { - // we need a better observable API to deregister callbacks, - // Right now one can only deregister a callback from within the callback by returning false. - // So we notify the whole texture atlas with the texture that needs to go & deregister. - const data = TEXTURE_ATLAS[0].value; - TEXTURE_ATLAS[0].notify(scene.screen.texture_atlas, true); - TEXTURE_ATLAS[0].value = data; - scene.screen.texture_atlas = undefined; - } - delete_three_scene(scene); - renderer.state.reset(); - renderer.dispose(); - return false; - } - // dont render invisible scenes - if (!scene.visible.value) { - return true; - } - renderer.autoClear = scene.clearscene.value; - const area = scene.viewport.value; - if (area) { - const [x, y, w, h] = area.map((x) => x * px_per_unit); - renderer.setViewport(x, y, w, h); - renderer.setScissor(x, y, w, h); - renderer.setScissorTest(true); - if (picking) { - renderer.setClearAlpha(0); - renderer.setClearColor(new THREE.Color(0), 0.0); - } else { - const alpha = scene.backgroundcolor_alpha.value; - renderer.setClearColor(scene.backgroundcolor.value, alpha); - } - renderer.render(scene, camera); - } - return scene.scene_children.every((x) => render_scene(x, picking)); -} - -function start_renderloop(three_scene) { - // extract the first scene for screen, which should be shared by all scenes! - const { fps } = three_scene.screen; - const time_per_frame = (1 / fps) * 1000; // default is 30 fps - // make sure we immediately render the first frame and dont wait 30ms - let last_time_stamp = performance.now(); - function renderloop(timestamp) { - if (timestamp - last_time_stamp > time_per_frame) { - const all_rendered = render_scene(three_scene); - if (!all_rendered) { - // if scenes don't render it means they're not displayed anymore - // - time to quit the renderin' business - return; - } - last_time_stamp = performance.now(); - } - window.requestAnimationFrame(renderloop); - } - // render one time before starting loop, so that we don't wait 30ms before first render - render_scene(three_scene); - renderloop(); -} - -// from: https://www.geeksforgeeks.org/javascript-throttling/ -function throttle_function(func, delay) { - // Previously called time of the function - let prev = 0; - // ID of queued future update - let future_id = undefined; - function inner_throttle(...args) { - // Current called time of the function - const now = new Date().getTime(); - - // If we had a queued run, clear it now, we're - // either going to execute now, or queue a new run. - if (future_id !== undefined) { - clearTimeout(future_id); - future_id = undefined; - } - - // If difference is greater than delay call - // the function again. - if (now - prev > delay) { - prev = now; - // "..." is the spread operator here - // returning the function with the - // array of arguments - return func(...args); - } else { - // Otherwise, we want to queue this function call - // to occur at some later later time, so that it - // does not get lost; we'll schedule it so that it - // fires just a bit after our choke ends. - future_id = setTimeout( - () => inner_throttle(...args), - now - prev + 1 - ); - } - } - return inner_throttle; -} - -function get_body_size() { - const bodyStyle = window.getComputedStyle(document.body); - // Subtract padding that is added by VSCode - const width_padding = - parseInt(bodyStyle.paddingLeft, 10) + - parseInt(bodyStyle.paddingRight, 10) + - parseInt(bodyStyle.marginLeft, 10) + - parseInt(bodyStyle.marginRight, 10); - const height_padding = - parseInt(bodyStyle.paddingTop, 10) + - parseInt(bodyStyle.paddingBottom, 10) + - parseInt(bodyStyle.marginTop, 10) + - parseInt(bodyStyle.marginBottom, 10); - const width = (window.innerWidth - width_padding); - const height = (window.innerHeight - height_padding); - return [width, height]; -} -function get_parent_size(canvas) { - const rect = canvas.parentElement.getBoundingClientRect(); - return [rect.width, rect.height]; -} - -export function wglerror(gl, error) { - switch (error) { - case gl.NO_ERROR: - return "No error"; - case gl.INVALID_ENUM: - return "Invalid enum"; - case gl.INVALID_VALUE: - return "Invalid value"; - case gl.INVALID_OPERATION: - return "Invalid operation"; - case gl.OUT_OF_MEMORY: - return "Out of memory"; - case gl.CONTEXT_LOST_WEBGL: - return "Context lost"; - default: - return "Unknown error"; - } -} -// taken from THREEJS: -//https://github.com/mrdoob/three.js/blob/5303ef2d46b02e7c503ca63cedca0b93cd9c853e/src/renderers/webgl/WebGLProgram.js#L67C1-L89C2 -function handleSource(string, errorLine) { - const lines = string.split("\n"); - const lines2 = []; - - const from = Math.max(errorLine - 6, 0); - const to = Math.min(errorLine + 6, lines.length); - - for (let i = from; i < to; i++) { - const line = i + 1; - lines2.push(`${line === errorLine ? ">" : " "} ${line}: ${lines[i]}`); - } - - return lines2.join("\n"); -} - -function getShaderErrors(gl, shader, type) { - const status = gl.getShaderParameter(shader, gl.COMPILE_STATUS); - const errors = gl.getShaderInfoLog(shader).trim(); - - if (status && errors === "") return ""; - - const errorMatches = /ERROR: 0:(\d+)/.exec(errors); - if (errorMatches) { - // --enable-privileged-webgl-extension - // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); - - const errorLine = parseInt(errorMatches[1]); - return ( - type.toUpperCase() + - "\n\n" + - errors + - "\n\n" + - handleSource(gl.getShaderSource(shader), errorLine) - ); - } else { - return errors; - } -} -function on_shader_error(gl, program, glVertexShader, glFragmentShader) { - const programLog = gl.getProgramInfoLog(program).trim(); - const vertexErrors = getShaderErrors(gl, glVertexShader, "vertex"); - const fragmentErrors = getShaderErrors(gl, glFragmentShader, "fragment"); - const vertexLog = gl.getShaderInfoLog(glVertexShader).trim(); - const fragmentLog = gl.getShaderInfoLog(glFragmentShader).trim(); - - const err = - "THREE.WebGLProgram: Shader Error " + - wglerror(gl, gl.getError()) + - " - " + - "VALIDATE_STATUS " + - gl.getProgramParameter(program, gl.VALIDATE_STATUS) + - "\n\n" + - "Program Info Log:\n" + - programLog + - "\n" + - vertexErrors + - "\n" + - fragmentErrors + - "\n" + - "Fragment log:\n" + - fragmentLog + - "Vertex log:\n" + - vertexLog; - - Bonito.Connection.send_warning(err); -} - -function add_canvas_events(screen, comm, resize_to) { - const { canvas, winscale } = screen; - function mouse_callback(event) { - const [x, y] = events2unitless(screen, event); - comm.notify({ mouseposition: [x, y] }); - } - - const notify_mouse_throttled = throttle_function(mouse_callback, 40); - - function mousemove(event) { - notify_mouse_throttled(event); - return false; - } - - canvas.addEventListener("mousemove", mousemove); - - function mousedown(event) { - comm.notify({ - mousedown: event.buttons, - }); - return false; - } - canvas.addEventListener("mousedown", mousedown); - - function mouseup(event) { - comm.notify({ - mouseup: event.buttons, - }); - return false; - } - - canvas.addEventListener("mouseup", mouseup); - - function wheel(event) { - comm.notify({ - scroll: [event.deltaX, -event.deltaY], - }); - event.preventDefault(); - return false; - } - canvas.addEventListener("wheel", wheel); - - function keydown(event) { - comm.notify({ - keydown: event.code, - }); - return false; - } - - canvas.addEventListener("keydown", keydown); - - function keyup(event) { - comm.notify({ - keyup: event.code, - }); - return false; - } - - canvas.addEventListener("keyup", keyup); - // This is a pretty ugly work around...... - // so on keydown, we add the key to the currently pressed keys set - // if we open the contextmenu before releasing the key, we'll never - // receive an up event, so the key will stay inside the currently_pressed - // set... Only option I found is to actually listen to the contextmenu - // and remove all keys if its opened. - function contextmenu(event) { - comm.notify({ - keyup: "delete_keys", - }); - return false; - } - - canvas.addEventListener("contextmenu", (e) => e.preventDefault()); - canvas.addEventListener("focusout", contextmenu); - - function resize_callback() { - let width, height; - if (resize_to == "body") { - [width, height] = get_body_size(); - } else if (resize_to == "parent") { - [width, height] = get_parent_size(canvas); - } - // Send the resize event to Julia - comm.notify({ resize: [width / winscale, height / winscale] }); - } - if (resize_to) { - const resize_callback_throttled = throttle_function( - resize_callback, - 100 - ); - window.addEventListener("resize", (event) => - resize_callback_throttled() - ); - // Fire the resize event once at the start to auto-size our window - resize_callback_throttled(); - } -} - -function threejs_module(canvas) { - - let context = canvas.getContext("webgl2", { - preserveDrawingBuffer: true, - }); - if (!context) { - console.warn( - "WebGL 2.0 not supported by browser, falling back to WebGL 1.0 (Volume plots will not work)" - ); - context = canvas.getContext("webgl", { - preserveDrawingBuffer: true, - }); - } - if (!context) { - // Sigh, safari or something - // we return nothing which will be handled by caller - return; - } - - const renderer = new THREE.WebGLRenderer({ - antialias: true, - canvas: canvas, - context: context, - powerPreference: "high-performance", - precision: "highp", - alpha: true, - logarithmicDepthBuffer: true - }); - - renderer.debug.onShaderError = on_shader_error; - renderer.setClearColor("#ffffff"); - - return renderer; -} - -function set_render_size(screen, width, height) { - const { renderer, canvas, scalefactor, winscale, px_per_unit } = screen; - // The displayed size of the canvas, in CSS pixels - which get scaled by the device pixel ratio - const [swidth, sheight] = [winscale * width, winscale * height]; - - const real_pixel_width = Math.ceil(width * px_per_unit); - const real_pixel_height = Math.ceil(height * px_per_unit); - - renderer._width = width; - renderer._height = height; - - canvas.width = real_pixel_width; - canvas.height = real_pixel_height; - - canvas.style.width = swidth + "px"; - canvas.style.height = sheight + "px"; - - renderer.setViewport(0, 0, real_pixel_width, real_pixel_height); - add_picking_target(screen); - return; -} - -function add_picking_target(screen) { - const { picking_target, canvas } = screen; - const [w, h] = [canvas.width, canvas.height]; - if (picking_target) { - if (picking_target.width == w && picking_target.height == h) { - return - } else { - picking_target.dispose(); - } - } - // BIG TODO here... - // We should only make the picking target as big as the area we're picking - // e.g. for just the mouse position it should be 1x1 - // Or we should just always bind the target and render to it in one pass - // 1) One Pass: - // Only works on WebGL 2.0, which is still not as widely supported - // Also it's a bit more complicated to setup - // 2) Only Area we pick - // It's currently not as easy to change the offset + area of the camera - // So, we'll need to make that easier first - screen.picking_target = new THREE.WebGLRenderTarget(w, h); - return; -} - -function create_scene( - wrapper, - canvas, - canvas_width, - scenes, - comm, - width, - height, - texture_atlas_obs, - fps, - resize_to, - px_per_unit, - scalefactor -) { - if (!scalefactor) { - scalefactor = window.devicePixelRatio || 1.0; - } - if (!px_per_unit) { - px_per_unit = scalefactor; - } - - const renderer = threejs_module(canvas); - - TEXTURE_ATLAS[0] = texture_atlas_obs; - - if (!renderer) { - const warning = getWebGLErrorMessage(); - // wrapper.removeChild(canvas) - wrapper.appendChild(warning); - } - - const camera = new THREE.PerspectiveCamera(45, 1, 0, 100); - camera.updateProjectionMatrix(); - const pixel_ratio = window.devicePixelRatio || 1.0; - const winscale = scalefactor / pixel_ratio; - const screen = { - renderer, - camera, - fps, - canvas, - px_per_unit, - scalefactor, - winscale, - texture_atlas: undefined - }; - add_canvas_events(screen, comm, resize_to); - set_render_size(screen, width, height); - - const three_scene = deserialize_scene(scenes, screen); - - start_renderloop(three_scene); - - canvas_width.on((w_h) => { - // `renderer.setSize` correctly updates `canvas` dimensions - set_render_size(screen, ...w_h); - }); - return renderer; -} - -function set_picking_uniforms( - scene, - last_id, - picking, - picked_plots, - plots, - id_to_plot -) { - scene.children.forEach((plot, index) => { - const { material } = plot; - const { uniforms } = material; - if (picking) { - uniforms.object_id.value = last_id + index; - uniforms.picking.value = true; - material.blending = THREE.NoBlending; - } else { - // clean up after picking - uniforms.picking.value = false; - material.blending = THREE.NormalBlending; - // we also collect the picked/matched plots as part of the clean up - const id = uniforms.object_id.value; - if (id in picked_plots) { - plots.push([plot, picked_plots[id]]); - id_to_plot[id] = plot; // create mapping from id to plot at the same time - } - } - }); - let next_id = last_id + scene.children.length; - scene.scene_children.forEach((scene) => { - next_id = set_picking_uniforms( - scene, - next_id, - picking, - picked_plots, - plots, - id_to_plot - ); - }); - return next_id; -} - -/** - * - * @param {*} scene - * @param {*} x in scene unitless pixel space - * @param {*} y in scene unitless pixel space - * @param {*} w in scene unitless pixel space - * @param {*} h in scene unitless pixel space - * @returns - */ -export function pick_native(scene, _x, _y, _w, _h) { - const { renderer, picking_target, px_per_unit } = scene.screen; - [_x, _y, _w, _h] = [_x, _y, _w, _h].map((x) => Math.ceil(x * px_per_unit)); - const [x, y, w, h] = [_x, _y, _w, _h]; - // render the scene - renderer.setRenderTarget(picking_target); - set_picking_uniforms(scene, 1, true); - render_scene(scene, true); - renderer.setRenderTarget(null); // reset render target - const nbytes = w * h * 4; - const pixel_bytes = new Uint8Array(nbytes); - //read the pixel - renderer.readRenderTargetPixels( - picking_target, - x, // x - y, // y - w, // width - h, // height - pixel_bytes - ); - - - const picked_plots = {}; - const picked_plots_array = []; - - const reinterpret_view = new DataView(pixel_bytes.buffer); - - for (let i = 0; i < pixel_bytes.length / 4; i++) { - const id = reinterpret_view.getUint16(i * 4); - const index = reinterpret_view.getUint16(i * 4 + 2); - picked_plots_array.push([id, index]); - picked_plots[id] = index; - } - // dict of plot_uuid => primitive_index (e.g. instance id or triangle index) - const plots = []; - const id_to_plot = {}; - set_picking_uniforms(scene, 0, false, picked_plots, plots, id_to_plot); - const picked_plots_matrix = picked_plots_array.map(([id, index]) => { - const p = id_to_plot[id]; - return [p ? p.plot_uuid : null, index]; - }); - const plot_matrix = { data: picked_plots_matrix, size: [w, h] }; - - return [plot_matrix, plots]; -} - -export function pick_closest(scene, xy, range) { - const { renderer } = scene.screen; - const [ width, height ] = [renderer._width, renderer._height]; - - if (!(1.0 <= xy[0] <= width && 1.0 <= xy[1] <= height)) { - return [null, 0]; - } - - const x0 = Math.max(1, xy[0] - range); - const y0 = Math.max(1, xy[1] - range); - const x1 = Math.min(width, Math.floor(xy[0] + range)); - const y1 = Math.min(height, Math.floor(xy[1] + range)); - - const dx = x1 - x0; - const dy = y1 - y0; - const [plot_data, _] = pick_native(scene, x0, y0, dx, dy); - const plot_matrix = plot_data.data; - let min_dist = range ^ 2; - let selection = [null, 0]; - const x = xy[0] + 1 - x0; - const y = xy[1] + 1 - y0; - let pindex = 0; - for (let i = 1; i <= dx; i++) { - for (let j = 1; j <= dx; j++) { - const d = (x - i) ^ (2 + (y - j)) ^ 2; - const [plot_uuid, index] = plot_matrix[pindex]; - pindex = pindex + 1; - if (d < min_dist && plot_uuid) { - min_dist = d; - selection = [plot_uuid, index]; - } - } - } - return selection; -} - -export function pick_sorted(scene, xy, range) { - const { renderer } = scene.screen; - const [width, height] = [renderer._width, renderer._height]; - - if (!(1.0 <= xy[0] <= width && 1.0 <= xy[1] <= height)) { - return null; - } - - const x0 = Math.max(1, xy[0] - range); - const y0 = Math.max(1, xy[1] - range); - const x1 = Math.min(width, Math.floor(xy[0] + range)); - const y1 = Math.min(height, Math.floor(xy[1] + range)); - - const dx = x1 - x0; - const dy = y1 - y0; - - const [plot_data, selected] = pick_native(scene, x0, y0, dx, dy); - if (selected.length == 0) { - return null; - } - const plot_matrix = plot_data.data; - const distances = selected.map((x) => range ^ 2); - const x = xy[0] + 1 - x0; - const y = xy[1] + 1 - y0; - let pindex = 0; - for (let i = 1; i <= dx; i++) { - for (let j = 1; j <= dx; j++) { - const d = (x - i) ^ (2 + (y - j)) ^ 2; - if (plot_matrix.length <= pindex) { - continue; - } - const [plot_uuid, index] = plot_matrix[pindex]; - pindex = pindex + 1; - const plot_index = selected.findIndex( - (x) => x[0].plot_uuid == plot_uuid - ); - if (plot_index >= 0 && d < distances[plot_index]) { - distances[plot_index] = d; - } - } - } - - const sorted_indices = Array.from(Array(distances.length).keys()).sort( - (a, b) => - distances[a] < distances[b] ? -1 : (distances[b] < distances[a]) | 0 - ); - return sorted_indices.map((idx) => { - const [plot, index] = selected[idx]; - return [plot.plot_uuid, index]; - }); -} - -export function pick_native_uuid(scene, x, y, w, h) { - const [_, picked_plots] = pick_native(scene, x, y, w, h); - return picked_plots.map(([p, index]) => [p.plot_uuid, index]); -} - -export function pick_native_matrix(scene, x, y, w, h) { - const [matrix, _] = pick_native(scene, x, y, w, h); - return matrix; -} - -export function register_popup(popup, scene, plots_to_pick, callback) { - if (!scene || !scene.screen) { - // scene not innitialized or removed already - return; - } - const { canvas } = scene.screen; - canvas.addEventListener("mousedown", (event) => { - const [x, y] = events2unitless(scene.screen, event); - const [_, picks] = pick_native(scene, x, y, 1, 1); - if (picks.length == 1) { - const [plot, index] = picks[0]; - if (plots_to_pick.has(plot.plot_uuid)) { - const result = callback(plot, index); - if (!popup.classList.contains("show")) { - popup.classList.add("show"); - } - popup.style.left = event.pageX + "px"; - popup.style.top = event.pageY + "px"; - if (typeof result === "string" || result instanceof String) { - popup.innerText = result; - } else { - popup.innerHTML = result; - } - } - } else { - popup.classList.remove("show"); - } - }); - canvas.addEventListener("keyup", (event) => { - if (event.key === "Escape") { - popup.classList.remove("show"); - } - }); -} - -window.WGL = { - deserialize_scene, - threejs_module, - start_renderloop, - delete_plots, - insert_plot, - find_plots, - delete_scene, - find_scene, - scene_cache, - plot_cache, - delete_scenes, - create_scene, - events2unitless, - on_next_insert, - register_popup, - render_scene, - TEXTURE_ATLAS, -}; - -export { - deserialize_scene, - threejs_module, - start_renderloop, - delete_plots, - insert_plot, - find_plots, - delete_scene, - find_scene, - scene_cache, - plot_cache, - delete_scenes, - create_scene, - events2unitless, - on_next_insert, -}; From 223fea0a23e7088af1a1228d704c870a3e5254ad Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 25 Apr 2024 15:13:06 +0200 Subject: [PATCH 31/37] restore file --- WGLMakie/src/wglmakie.js | 737 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 737 insertions(+) create mode 100644 WGLMakie/src/wglmakie.js diff --git a/WGLMakie/src/wglmakie.js b/WGLMakie/src/wglmakie.js new file mode 100644 index 00000000000..d023b735243 --- /dev/null +++ b/WGLMakie/src/wglmakie.js @@ -0,0 +1,737 @@ +import * as THREE from "./THREE.js"; +import { getWebGLErrorMessage } from "./WEBGL.js"; +import { + delete_scenes, + insert_plot, + delete_plots, + delete_scene, + delete_three_scene, + find_plots, + deserialize_scene, + TEXTURE_ATLAS, + on_next_insert, + scene_cache, + plot_cache, + find_scene, +} from "./Serialization.js"; + +import { events2unitless } from "./Camera.js"; + +window.THREE = THREE; + +export function render_scene(scene, picking = false) { + const { camera, renderer, px_per_unit } = scene.screen; + const canvas = renderer.domElement; + if (!document.body.contains(canvas)) { + console.log("removing WGL context, canvas is not in the DOM anymore!"); + if (scene.screen.texture_atlas) { + // we need a better observable API to deregister callbacks, + // Right now one can only deregister a callback from within the callback by returning false. + // So we notify the whole texture atlas with the texture that needs to go & deregister. + const data = TEXTURE_ATLAS[0].value; + TEXTURE_ATLAS[0].notify(scene.screen.texture_atlas, true); + TEXTURE_ATLAS[0].value = data; + scene.screen.texture_atlas = undefined; + } + delete_three_scene(scene); + renderer.state.reset(); + renderer.dispose(); + return false; + } + // dont render invisible scenes + if (!scene.visible.value) { + return true; + } + renderer.autoClear = scene.clearscene.value; + const area = scene.viewport.value; + if (area) { + const [x, y, w, h] = area.map((x) => x * px_per_unit); + renderer.setViewport(x, y, w, h); + renderer.setScissor(x, y, w, h); + renderer.setScissorTest(true); + if (picking) { + renderer.setClearAlpha(0); + renderer.setClearColor(new THREE.Color(0), 0.0); + } else { + const alpha = scene.backgroundcolor_alpha.value; + renderer.setClearColor(scene.backgroundcolor.value, alpha); + } + renderer.render(scene, camera); + } + return scene.scene_children.every((x) => render_scene(x, picking)); +} + +function start_renderloop(three_scene) { + // extract the first scene for screen, which should be shared by all scenes! + const { fps } = three_scene.screen; + const time_per_frame = (1 / fps) * 1000; // default is 30 fps + // make sure we immediately render the first frame and dont wait 30ms + let last_time_stamp = performance.now(); + function renderloop(timestamp) { + if (timestamp - last_time_stamp > time_per_frame) { + const all_rendered = render_scene(three_scene); + if (!all_rendered) { + // if scenes don't render it means they're not displayed anymore + // - time to quit the renderin' business + return; + } + last_time_stamp = performance.now(); + } + window.requestAnimationFrame(renderloop); + } + // render one time before starting loop, so that we don't wait 30ms before first render + render_scene(three_scene); + renderloop(); +} + +// from: https://www.geeksforgeeks.org/javascript-throttling/ +function throttle_function(func, delay) { + // Previously called time of the function + let prev = 0; + // ID of queued future update + let future_id = undefined; + function inner_throttle(...args) { + // Current called time of the function + const now = new Date().getTime(); + + // If we had a queued run, clear it now, we're + // either going to execute now, or queue a new run. + if (future_id !== undefined) { + clearTimeout(future_id); + future_id = undefined; + } + + // If difference is greater than delay call + // the function again. + if (now - prev > delay) { + prev = now; + // "..." is the spread operator here + // returning the function with the + // array of arguments + return func(...args); + } else { + // Otherwise, we want to queue this function call + // to occur at some later later time, so that it + // does not get lost; we'll schedule it so that it + // fires just a bit after our choke ends. + future_id = setTimeout( + () => inner_throttle(...args), + now - prev + 1 + ); + } + } + return inner_throttle; +} + +function get_body_size() { + const bodyStyle = window.getComputedStyle(document.body); + // Subtract padding that is added by VSCode + const width_padding = + parseInt(bodyStyle.paddingLeft, 10) + + parseInt(bodyStyle.paddingRight, 10) + + parseInt(bodyStyle.marginLeft, 10) + + parseInt(bodyStyle.marginRight, 10); + const height_padding = + parseInt(bodyStyle.paddingTop, 10) + + parseInt(bodyStyle.paddingBottom, 10) + + parseInt(bodyStyle.marginTop, 10) + + parseInt(bodyStyle.marginBottom, 10); + const width = (window.innerWidth - width_padding); + const height = (window.innerHeight - height_padding); + return [width, height]; +} +function get_parent_size(canvas) { + const rect = canvas.parentElement.getBoundingClientRect(); + return [rect.width, rect.height]; +} + +export function wglerror(gl, error) { + switch (error) { + case gl.NO_ERROR: + return "No error"; + case gl.INVALID_ENUM: + return "Invalid enum"; + case gl.INVALID_VALUE: + return "Invalid value"; + case gl.INVALID_OPERATION: + return "Invalid operation"; + case gl.OUT_OF_MEMORY: + return "Out of memory"; + case gl.CONTEXT_LOST_WEBGL: + return "Context lost"; + default: + return "Unknown error"; + } +} +// taken from THREEJS: +//https://github.com/mrdoob/three.js/blob/5303ef2d46b02e7c503ca63cedca0b93cd9c853e/src/renderers/webgl/WebGLProgram.js#L67C1-L89C2 +function handleSource(string, errorLine) { + const lines = string.split("\n"); + const lines2 = []; + + const from = Math.max(errorLine - 6, 0); + const to = Math.min(errorLine + 6, lines.length); + + for (let i = from; i < to; i++) { + const line = i + 1; + lines2.push(`${line === errorLine ? ">" : " "} ${line}: ${lines[i]}`); + } + + return lines2.join("\n"); +} + +function getShaderErrors(gl, shader, type) { + const status = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + const errors = gl.getShaderInfoLog(shader).trim(); + + if (status && errors === "") return ""; + + const errorMatches = /ERROR: 0:(\d+)/.exec(errors); + if (errorMatches) { + // --enable-privileged-webgl-extension + // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); + + const errorLine = parseInt(errorMatches[1]); + return ( + type.toUpperCase() + + "\n\n" + + errors + + "\n\n" + + handleSource(gl.getShaderSource(shader), errorLine) + ); + } else { + return errors; + } +} +function on_shader_error(gl, program, glVertexShader, glFragmentShader) { + const programLog = gl.getProgramInfoLog(program).trim(); + const vertexErrors = getShaderErrors(gl, glVertexShader, "vertex"); + const fragmentErrors = getShaderErrors(gl, glFragmentShader, "fragment"); + const vertexLog = gl.getShaderInfoLog(glVertexShader).trim(); + const fragmentLog = gl.getShaderInfoLog(glFragmentShader).trim(); + + const err = + "THREE.WebGLProgram: Shader Error " + + wglerror(gl, gl.getError()) + + " - " + + "VALIDATE_STATUS " + + gl.getProgramParameter(program, gl.VALIDATE_STATUS) + + "\n\n" + + "Program Info Log:\n" + + programLog + + "\n" + + vertexErrors + + "\n" + + fragmentErrors + + "\n" + + "Fragment log:\n" + + fragmentLog + + "Vertex log:\n" + + vertexLog; + + Bonito.Connection.send_warning(err); +} + +function add_canvas_events(screen, comm, resize_to) { + const { canvas, winscale } = screen; + function mouse_callback(event) { + const [x, y] = events2unitless(screen, event); + comm.notify({ mouseposition: [x, y] }); + } + + const notify_mouse_throttled = throttle_function(mouse_callback, 40); + + function mousemove(event) { + notify_mouse_throttled(event); + return false; + } + + canvas.addEventListener("mousemove", mousemove); + + function mousedown(event) { + comm.notify({ + mousedown: event.buttons, + }); + return false; + } + canvas.addEventListener("mousedown", mousedown); + + function mouseup(event) { + comm.notify({ + mouseup: event.buttons, + }); + return false; + } + + canvas.addEventListener("mouseup", mouseup); + + function wheel(event) { + comm.notify({ + scroll: [event.deltaX, -event.deltaY], + }); + event.preventDefault(); + return false; + } + canvas.addEventListener("wheel", wheel); + + function keydown(event) { + comm.notify({ + keydown: event.code, + }); + return false; + } + + canvas.addEventListener("keydown", keydown); + + function keyup(event) { + comm.notify({ + keyup: event.code, + }); + return false; + } + + canvas.addEventListener("keyup", keyup); + // This is a pretty ugly work around...... + // so on keydown, we add the key to the currently pressed keys set + // if we open the contextmenu before releasing the key, we'll never + // receive an up event, so the key will stay inside the currently_pressed + // set... Only option I found is to actually listen to the contextmenu + // and remove all keys if its opened. + function contextmenu(event) { + comm.notify({ + keyup: "delete_keys", + }); + return false; + } + + canvas.addEventListener("contextmenu", (e) => e.preventDefault()); + canvas.addEventListener("focusout", contextmenu); + + function resize_callback() { + let width, height; + if (resize_to == "body") { + [width, height] = get_body_size(); + } else if (resize_to == "parent") { + [width, height] = get_parent_size(canvas); + } + // Send the resize event to Julia + comm.notify({ resize: [width / winscale, height / winscale] }); + } + if (resize_to) { + const resize_callback_throttled = throttle_function( + resize_callback, + 100 + ); + window.addEventListener("resize", (event) => + resize_callback_throttled() + ); + // Fire the resize event once at the start to auto-size our window + resize_callback_throttled(); + } +} + +function threejs_module(canvas) { + + let context = canvas.getContext("webgl2", { + preserveDrawingBuffer: true, + }); + if (!context) { + console.warn( + "WebGL 2.0 not supported by browser, falling back to WebGL 1.0 (Volume plots will not work)" + ); + context = canvas.getContext("webgl", { + preserveDrawingBuffer: true, + }); + } + if (!context) { + // Sigh, safari or something + // we return nothing which will be handled by caller + return; + } + + const renderer = new THREE.WebGLRenderer({ + antialias: true, + canvas: canvas, + context: context, + powerPreference: "high-performance", + precision: "highp", + alpha: true, + logarithmicDepthBuffer: true + }); + + renderer.debug.onShaderError = on_shader_error; + renderer.setClearColor("#ffffff"); + + return renderer; +} + +function set_render_size(screen, width, height) { + const { renderer, canvas, scalefactor, winscale, px_per_unit } = screen; + // The displayed size of the canvas, in CSS pixels - which get scaled by the device pixel ratio + const [swidth, sheight] = [winscale * width, winscale * height]; + + const real_pixel_width = Math.ceil(width * px_per_unit); + const real_pixel_height = Math.ceil(height * px_per_unit); + + renderer._width = width; + renderer._height = height; + + canvas.width = real_pixel_width; + canvas.height = real_pixel_height; + + canvas.style.width = swidth + "px"; + canvas.style.height = sheight + "px"; + + renderer.setViewport(0, 0, real_pixel_width, real_pixel_height); + add_picking_target(screen); + return; +} + +function add_picking_target(screen) { + const { picking_target, canvas } = screen; + const [w, h] = [canvas.width, canvas.height]; + if (picking_target) { + if (picking_target.width == w && picking_target.height == h) { + return + } else { + picking_target.dispose(); + } + } + // BIG TODO here... + // We should only make the picking target as big as the area we're picking + // e.g. for just the mouse position it should be 1x1 + // Or we should just always bind the target and render to it in one pass + // 1) One Pass: + // Only works on WebGL 2.0, which is still not as widely supported + // Also it's a bit more complicated to setup + // 2) Only Area we pick + // It's currently not as easy to change the offset + area of the camera + // So, we'll need to make that easier first + screen.picking_target = new THREE.WebGLRenderTarget(w, h); + return; +} + +function create_scene( + wrapper, + canvas, + canvas_width, + scenes, + comm, + width, + height, + texture_atlas_obs, + fps, + resize_to, + px_per_unit, + scalefactor +) { + if (!scalefactor) { + scalefactor = window.devicePixelRatio || 1.0; + } + if (!px_per_unit) { + px_per_unit = scalefactor; + } + + const renderer = threejs_module(canvas); + + TEXTURE_ATLAS[0] = texture_atlas_obs; + + if (!renderer) { + const warning = getWebGLErrorMessage(); + // wrapper.removeChild(canvas) + wrapper.appendChild(warning); + } + + const camera = new THREE.PerspectiveCamera(45, 1, 0, 100); + camera.updateProjectionMatrix(); + const pixel_ratio = window.devicePixelRatio || 1.0; + const winscale = scalefactor / pixel_ratio; + const screen = { + renderer, + camera, + fps, + canvas, + px_per_unit, + scalefactor, + winscale, + texture_atlas: undefined + }; + add_canvas_events(screen, comm, resize_to); + set_render_size(screen, width, height); + + const three_scene = deserialize_scene(scenes, screen); + + start_renderloop(three_scene); + + canvas_width.on((w_h) => { + // `renderer.setSize` correctly updates `canvas` dimensions + set_render_size(screen, ...w_h); + }); + return renderer; +} + +function set_picking_uniforms( + scene, + last_id, + picking, + picked_plots, + plots, + id_to_plot +) { + scene.children.forEach((plot, index) => { + const { material } = plot; + const { uniforms } = material; + if (picking) { + uniforms.object_id.value = last_id + index; + uniforms.picking.value = true; + material.blending = THREE.NoBlending; + } else { + // clean up after picking + uniforms.picking.value = false; + material.blending = THREE.NormalBlending; + // we also collect the picked/matched plots as part of the clean up + const id = uniforms.object_id.value; + if (id in picked_plots) { + plots.push([plot, picked_plots[id]]); + id_to_plot[id] = plot; // create mapping from id to plot at the same time + } + } + }); + let next_id = last_id + scene.children.length; + scene.scene_children.forEach((scene) => { + next_id = set_picking_uniforms( + scene, + next_id, + picking, + picked_plots, + plots, + id_to_plot + ); + }); + return next_id; +} + +/** + * + * @param {*} scene + * @param {*} x in scene unitless pixel space + * @param {*} y in scene unitless pixel space + * @param {*} w in scene unitless pixel space + * @param {*} h in scene unitless pixel space + * @returns + */ +export function pick_native(scene, _x, _y, _w, _h) { + const { renderer, picking_target, px_per_unit } = scene.screen; + [_x, _y, _w, _h] = [_x, _y, _w, _h].map((x) => Math.ceil(x * px_per_unit)); + const [x, y, w, h] = [_x, _y, _w, _h]; + // render the scene + renderer.setRenderTarget(picking_target); + set_picking_uniforms(scene, 1, true); + render_scene(scene, true); + renderer.setRenderTarget(null); // reset render target + const nbytes = w * h * 4; + const pixel_bytes = new Uint8Array(nbytes); + //read the pixel + renderer.readRenderTargetPixels( + picking_target, + x, // x + y, // y + w, // width + h, // height + pixel_bytes + ); + + + const picked_plots = {}; + const picked_plots_array = []; + + const reinterpret_view = new DataView(pixel_bytes.buffer); + + for (let i = 0; i < pixel_bytes.length / 4; i++) { + const id = reinterpret_view.getUint16(i * 4); + const index = reinterpret_view.getUint16(i * 4 + 2); + picked_plots_array.push([id, index]); + picked_plots[id] = index; + } + // dict of plot_uuid => primitive_index (e.g. instance id or triangle index) + const plots = []; + const id_to_plot = {}; + set_picking_uniforms(scene, 0, false, picked_plots, plots, id_to_plot); + const picked_plots_matrix = picked_plots_array.map(([id, index]) => { + const p = id_to_plot[id]; + return [p ? p.plot_uuid : null, index]; + }); + const plot_matrix = { data: picked_plots_matrix, size: [w, h] }; + + return [plot_matrix, plots]; +} + +export function pick_closest(scene, xy, range) { + const { renderer } = scene.screen; + const [ width, height ] = [renderer._width, renderer._height]; + + if (!(1.0 <= xy[0] <= width && 1.0 <= xy[1] <= height)) { + return [null, 0]; + } + + const x0 = Math.max(1, xy[0] - range); + const y0 = Math.max(1, xy[1] - range); + const x1 = Math.min(width, Math.floor(xy[0] + range)); + const y1 = Math.min(height, Math.floor(xy[1] + range)); + + const dx = x1 - x0; + const dy = y1 - y0; + const [plot_data, _] = pick_native(scene, x0, y0, dx, dy); + const plot_matrix = plot_data.data; + let min_dist = range ^ 2; + let selection = [null, 0]; + const x = xy[0] + 1 - x0; + const y = xy[1] + 1 - y0; + let pindex = 0; + for (let i = 1; i <= dx; i++) { + for (let j = 1; j <= dx; j++) { + const d = (x - i) ^ (2 + (y - j)) ^ 2; + const [plot_uuid, index] = plot_matrix[pindex]; + pindex = pindex + 1; + if (d < min_dist && plot_uuid) { + min_dist = d; + selection = [plot_uuid, index]; + } + } + } + return selection; +} + +export function pick_sorted(scene, xy, range) { + const { renderer } = scene.screen; + const [width, height] = [renderer._width, renderer._height]; + + if (!(1.0 <= xy[0] <= width && 1.0 <= xy[1] <= height)) { + return null; + } + + const x0 = Math.max(1, xy[0] - range); + const y0 = Math.max(1, xy[1] - range); + const x1 = Math.min(width, Math.floor(xy[0] + range)); + const y1 = Math.min(height, Math.floor(xy[1] + range)); + + const dx = x1 - x0; + const dy = y1 - y0; + + const [plot_data, selected] = pick_native(scene, x0, y0, dx, dy); + if (selected.length == 0) { + return null; + } + const plot_matrix = plot_data.data; + const distances = selected.map((x) => range ^ 2); + const x = xy[0] + 1 - x0; + const y = xy[1] + 1 - y0; + let pindex = 0; + for (let i = 1; i <= dx; i++) { + for (let j = 1; j <= dx; j++) { + const d = (x - i) ^ (2 + (y - j)) ^ 2; + if (plot_matrix.length <= pindex) { + continue; + } + const [plot_uuid, index] = plot_matrix[pindex]; + pindex = pindex + 1; + const plot_index = selected.findIndex( + (x) => x[0].plot_uuid == plot_uuid + ); + if (plot_index >= 0 && d < distances[plot_index]) { + distances[plot_index] = d; + } + } + } + + const sorted_indices = Array.from(Array(distances.length).keys()).sort( + (a, b) => + distances[a] < distances[b] ? -1 : (distances[b] < distances[a]) | 0 + ); + return sorted_indices.map((idx) => { + const [plot, index] = selected[idx]; + return [plot.plot_uuid, index]; + }); +} + +export function pick_native_uuid(scene, x, y, w, h) { + const [_, picked_plots] = pick_native(scene, x, y, w, h); + return picked_plots.map(([p, index]) => [p.plot_uuid, index]); +} + +export function pick_native_matrix(scene, x, y, w, h) { + const [matrix, _] = pick_native(scene, x, y, w, h); + return matrix; +} + +export function register_popup(popup, scene, plots_to_pick, callback) { + if (!scene || !scene.screen) { + // scene not innitialized or removed already + return; + } + const { canvas } = scene.screen; + canvas.addEventListener("mousedown", (event) => { + const [x, y] = events2unitless(scene.screen, event); + const [_, picks] = pick_native(scene, x, y, 1, 1); + if (picks.length == 1) { + const [plot, index] = picks[0]; + if (plots_to_pick.has(plot.plot_uuid)) { + const result = callback(plot, index); + if (!popup.classList.contains("show")) { + popup.classList.add("show"); + } + popup.style.left = event.pageX + "px"; + popup.style.top = event.pageY + "px"; + if (typeof result === "string" || result instanceof String) { + popup.innerText = result; + } else { + popup.innerHTML = result; + } + } + } else { + popup.classList.remove("show"); + } + }); + canvas.addEventListener("keyup", (event) => { + if (event.key === "Escape") { + popup.classList.remove("show"); + } + }); +} + +window.WGL = { + deserialize_scene, + threejs_module, + start_renderloop, + delete_plots, + insert_plot, + find_plots, + delete_scene, + find_scene, + scene_cache, + plot_cache, + delete_scenes, + create_scene, + events2unitless, + on_next_insert, + register_popup, + render_scene, + TEXTURE_ATLAS, +}; + +export { + deserialize_scene, + threejs_module, + start_renderloop, + delete_plots, + insert_plot, + find_plots, + delete_scene, + find_scene, + scene_cache, + plot_cache, + delete_scenes, + create_scene, + events2unitless, + on_next_insert, +}; From f23c76a52fad16484f9399f6d9d106d4c6f6c138 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 25 Apr 2024 18:02:18 +0200 Subject: [PATCH 32/37] try fix connected sphere --- GLMakie/assets/shader/lines.geom | 8 ++++---- WGLMakie/src/Lines.js | 5 +++-- WGLMakie/src/wglmakie.bundled.js | 24 ++++++++---------------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index e0c510d58fc..500df08f7f7 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -292,8 +292,8 @@ void main(void) // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); - vec2 miter_n1 = miter.x < -0.4 ? sign(dot(v0.xy, n1)) * normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = miter.y < -0.4 ? sign(dot(v1.xy, n2)) * normalize(v1.xy - v2.xy) : normalize(n1 + n2); + vec2 miter_n1 = miter.x < -0.0 ? sign(dot(v0.xy, n1)) * normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < -0.0 ? sign(dot(v1.xy, n2)) * normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow @@ -350,8 +350,8 @@ void main(void) // TODO: skipping this for linestart/end avoid round and square being cut off // but causes overlap... vec2 shape_factor = (isvalid[0] && isvalid[3]) || (linecap == BUTT) ? vec2( - segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0][0] - extrusion[1][0])), // -n - segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0][1] - extrusion[1][1])) // +n + max(0.0, segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0][0] - extrusion[1][0]))), // -n + max(0.0, segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0][1] - extrusion[1][1]))) // +n ) : vec2(1.0); // Generate static/flat outputs diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index 345a302c253..39e9d5587d8 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -491,7 +491,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // TODO: skipping this for linestart/end avoid round and square // being cut off but causes overlap... float shape_factor = 1.0; - if ((isvalid[0] && isvalid[3]) || (linecap == BUTT)) + if ((isvalid[0] && isvalid[3]) || (int(linecap) == BUTT)) shape_factor = segment_length / max(segment_length, (halfwidth + AA_THICKNESS) * (extrusion[0] - extrusion[1])); @@ -803,7 +803,7 @@ function lines_fragment_shader(uniforms, attributes) { float sdf; // f_quad_sdf.x includes everything from p1 in p2-p1 direction, i.e. > - // f_quad_sdf2.y includes everything from p2 in p1-p2 direction, i.e. < + // f_quad_sdf.y includes everything from p2 in p1-p2 direction, i.e. < // < < | > < > < | > > // < < 1->----<->----<-2 > > // < < | > < > < | > > @@ -893,6 +893,7 @@ function lines_fragment_shader(uniforms, attributes) { // remaining overlap as softer red/blue if (discard_sdf1 - 1.0 > 0.0) color.r += 0.2; + color.r += 0.2; if (discard_sdf2 - 1.0 > 0.0) color.b += 0.2; diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 39e8991c797..63357f21399 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21765,21 +21765,12 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // '---' // To avoid drawing the "inverted" section we move the relevant // vertices to the crossing point (x) using this scaling factor. - float shape_factor = max( - 0.0, - segment_length / max( - segment_length, - (halfwidth + AA_THICKNESS) * (extrusion[0] - extrusion[1]) - ) - ); - - - // Don't use shape_vector on line starts/ends to avoid cutting of linecaps. - // (This is irrelevant for :butt) - // TODO: consider elongating :butt-ed lines and adjusting rendering for - // :round-ed lines to get a linestart/end. - shape_factor = isvalid[3 * int(is_end)] ? shape_factor : 1.0; - + // TODO: skipping this for linestart/end avoid round and square + // being cut off but causes overlap... + float shape_factor = 1.0; + if ((isvalid[0] && isvalid[3]) || (int(linecap) == BUTT)) + shape_factor = segment_length / max(segment_length, + (halfwidth + AA_THICKNESS) * (extrusion[0] - extrusion[1])); // If a pattern starts or stops drawing in a joint it will get // fractured across the joint. To avoid this we either: @@ -22091,7 +22082,7 @@ function lines_fragment_shader(uniforms, attributes) { float sdf; // f_quad_sdf.x includes everything from p1 in p2-p1 direction, i.e. > - // f_quad_sdf2.y includes everything from p2 in p1-p2 direction, i.e. < + // f_quad_sdf.y includes everything from p2 in p1-p2 direction, i.e. < // < < | > < > < | > > // < < 1->----<->----<-2 > > // < < | > < > < | > > @@ -22181,6 +22172,7 @@ function lines_fragment_shader(uniforms, attributes) { // remaining overlap as softer red/blue if (discard_sdf1 - 1.0 > 0.0) color.r += 0.2; + color.r += 0.2; if (discard_sdf2 - 1.0 > 0.0) color.b += 0.2; From 655c66257de4750f0a7023122dba887caa685d82 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 25 Apr 2024 18:55:43 +0200 Subject: [PATCH 33/37] add debug refimgs --- ReferenceTests/src/tests/examples3d.jl | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/ReferenceTests/src/tests/examples3d.jl b/ReferenceTests/src/tests/examples3d.jl index 890ddde407d..e63b7b42bdc 100644 --- a/ReferenceTests/src/tests/examples3d.jl +++ b/ReferenceTests/src/tests/examples3d.jl @@ -368,7 +368,11 @@ end large_sphere = Sphere(Point3f(0), 1f0) positions = decompose(Point3f, large_sphere) linepos = view(positions, RNG.rand(1:length(positions), 1000)) - fig, ax, lineplot = lines(linepos, linewidth=0.1, color=:black, transparency=true) + println(linepos.indices) + fig, ax, lineplot = lines( + linepos, linewidth=0.1, color=:black, transparency=true, + figure = (size = (5000, 5000),) + ) scatter!( ax, positions, markersize=10, strokewidth=2, strokecolor=:white, @@ -377,6 +381,31 @@ end fig end +@reference_test "Connected Sphere 2" begin + large_sphere = Sphere(Point3f(0), 1f0) + positions = decompose(Point3f, large_sphere) + idxs = [44, 4, 560, 214, 306, 185, 511, 186, 230, 375, 216, 494, 326, 38, 246, 129, 378, 560, 290, 427, 341, 361, 392, 198, 512, 396, 395, 5, 493, 325, 279, 198, 203, 75, 467, 240, 419, 167, 531, 509, 15, 533, 431, 542, 490, 271, 33, 11, 548, 443, 465, 119, 80, 339, 372, 380, 568, 430, 375, 258, 193, 80, 403, 311, 575, 470, 494, 41, 524, 233, 539, 561, 154, 199, 241, 145, 185, 264, 446, 213, 84, 252, 130, 103, 308, 403, 425, 286, 83, 563, 563, 317, 167, 335, 318, 122, 522, 392, 183, 329, 467, 472, 514, 149, 511, 282, 530, 178, 552, 55, 27, 281, 283, 361, 407, 189, 306, 99, 553, 60, 211, 197, 347, 527, 242, 467, 173, 3, 547, 516, 26, 308, 448, 61, 44, 423, 478, 357, 274, 255, 387, 168, 313, 166, 350, 172, 380, 405, 124, 383, 182, 219, 137, 153, 118, 424, 185, 240, 80, 302, 97, 433, 28, 166, 219, 502, 76, 485, 453, 507, 276, 18, 127, 122, 297, 270, 434, 128, 66, 506, 105, 270, 374, 555, 397, 181, 473, 192, 359, 223, 69, 162, 231, 547, 406, 561, 32, 421, 284, 515, 50, 348, 49, 18, 473, 195, 461, 42, 300, 356, 419, 105, 351, 289, 426, 60, 67, 52, 298, 244, 269, 259, 554, 80, 547, 249, 108, 373, 401, 514, 226, 333, 83, 135, 37, 89, 544, 362, 252, 85, 504, 169, 537, 17, 390, 111, 244, 53, 327, 203, 234, 282, 98, 325, 9, 484, 378, 87, 252, 75, 532, 205, 543, 77, 524, 309, 422, 179, 176, 166, 537, 177, 225, 314, 469, 364, 478, 449, 30, 18, 387, 142, 353, 332, 479, 199, 96, 331, 93, 24, 300, 182, 557, 408, 180, 316, 461, 455, 50, 471, 317, 288, 470, 354, 269, 88, 303, 53, 125, 331, 5, 280, 398, 307, 357, 174, 124, 476, 169, 4, 386, 138, 258, 75, 4, 113, 499, 346, 172, 356, 535, 530, 175, 94, 217, 351, 201, 36, 133, 216, 262, 575, 160, 215, 77, 162, 27, 273, 300, 428, 111, 488, 474, 330, 469, 231, 438, 61, 473, 551, 35, 260, 522, 394, 508, 210, 448, 353, 508, 439, 15, 172, 17, 34, 347, 18, 439, 124, 413, 526, 21, 74, 345, 556, 157, 271, 121, 566, 163, 323, 560, 33, 410, 142, 425, 336, 572, 3, 468, 231, 450, 424, 10, 133, 331, 245, 131, 156, 529, 150, 376, 101, 415, 467, 46, 143, 195, 272, 143, 8, 392, 476, 75, 416, 20, 128, 404, 178, 141, 267, 454, 151, 308, 230, 237, 392, 570, 174, 341, 220, 506, 446, 394, 182, 187, 106, 441, 93, 165, 330, 259, 335, 175, 184, 425, 140, 46, 166, 258, 106, 123, 125, 418, 355, 420, 384, 531, 521, 324, 301, 312, 13, 418, 382, 451, 73, 140, 269, 497, 196, 195, 165, 2, 121, 242, 51, 436, 68, 21, 530, 314, 528, 216, 315, 363, 5, 162, 318, 427, 26, 447, 245, 270, 254, 485, 302, 147, 340, 110, 228, 330, 127, 247, 142, 217, 479, 381, 476, 286, 155, 385, 378, 187, 180, 166, 193, 391, 560, 302, 21, 540, 212, 101, 141, 347, 423, 6, 282, 309, 547, 420, 563, 568, 256, 1, 298, 503, 74, 45, 468, 291, 133, 137, 220, 82, 575, 277, 267, 263, 454, 566, 34, 397, 445, 64, 504, 75, 6, 344, 331, 381, 273, 147, 354, 174, 181, 254, 11, 326, 302, 482, 272, 448, 200, 91, 283, 237, 554, 566, 324, 85, 287, 502, 547, 321, 182, 332, 60, 576, 242, 471, 490, 129, 332, 309, 455, 377, 511, 25, 122, 509, 510, 359, 340, 541, 379, 271, 412, 243, 166, 381, 345, 148, 15, 496, 98, 242, 137, 62, 42, 81, 283, 58, 241, 544, 335, 463, 478, 248, 17, 225, 76, 547, 74, 40, 97, 380, 429, 536, 259, 122, 205, 42, 66, 417, 498, 63, 298, 48, 366, 301, 216, 510, 240, 49, 172, 136, 152, 262, 107, 453, 572, 225, 184, 478, 94, 245, 272, 87, 345, 474, 392, 479, 250, 508, 500, 219, 260, 470, 512, 18, 564, 533, 162, 503, 169, 312, 54, 409, 520, 131, 155, 314, 440, 558, 231, 48, 373, 339, 155, 323, 476, 243, 18, 22, 541, 105, 531, 353, 205, 326, 88, 565, 427, 529, 238, 120, 386, 147, 130, 147, 375, 515, 395, 340, 536, 319, 191, 508, 510, 24, 115, 569, 57, 417, 285, 376, 26, 384, 462, 415, 237, 69, 532, 103, 501, 4, 288, 506, 64, 478, 18, 351, 237, 566, 228, 537, 49, 154, 445, 268, 563, 396, 508, 27, 277, 477, 225, 388, 460, 343, 223, 537, 40, 465, 311, 91, 142, 98, 365, 318, 22, 356, 175, 314, 513, 257, 471, 473, 559, 351, 89, 553, 165, 371, 119, 132, 201, 429, 124, 125, 47, 45, 540, 358, 180, 423, 350, 435, 525, 14, 67, 322, 345, 465, 264, 461, 53, 347, 102, 546, 327, 310, 58, 287, 151, 136, 448, 176, 554, 444, 160, 95, 89, 36, 65, 332, 409, 285, 187, 210, 190, 360, 302, 332, 486, 49, 316, 19, 278, 378, 327, 282, 424, 141, 159, 538, 223, 266, 103, 87, 535, 112, 427, 363, 382, 53, 22, 500, 527, 66, 520, 62, 244, 291, 289, 285, 492, 408, 416, 210, 524, 422, 545, 217, 13, 59, 172, 15, 146, 514, 463, 41, 213, 177, 482, 137, 320, 194, 295, 371, 449, 535, 280, 161, 19, 522, 4, 19, 547, 323, 444, 121, 168, 302, 490, 74, 560, 227, 266, 383, 53, 123, 557, 4, 371, 330, 301, 57, 406, 234, 329, 151, 485, 235, 240, 319, 155, 207, 11, 305, 253, 359, 231, 310, 152, 132, 512, 443, 36, 123, 380, 545, 358, 95, 231, 164, 451, 120, 42, 87, 1, 397, 163, 7, 213, 570, 452, 139, 389, 171, 508, 449, 392, 306, 401, 110, 56, 162, 245, 5, 322, 537, 381, 134, 97, 292, 291, 9, 571] + linepos = positions[idxs] + fig, ax, lineplot = lines( + linepos, linewidth=1, color=:black, transparency=true, + figure = (size = (5000, 5000),) + ) + fig +end + +@reference_test "Connected Sphere 3" begin + large_sphere = Sphere(Point3f(0), 1f0) + positions = decompose(Point3f, large_sphere) + idxs = [44, 4, 560, 214, 306, 185, 511, 186, 230, 375, 216, 494, 326, 38, 246, 129, 378, 560, 290, 427, 341, 361, 392, 198, 512, 396, 395, 5, 493, 325, 279, 198, 203, 75, 467, 240, 419, 167, 531, 509, 15, 533, 431, 542, 490, 271, 33, 11, 548, 443, 465, 119, 80, 339, 372, 380, 568, 430, 375, 258, 193, 80, 403, 311, 575, 470, 494, 41, 524, 233, 539, 561, 154, 199, 241, 145, 185, 264, 446, 213, 84, 252, 130, 103, 308, 403, 425, 286, 83, 563, 563, 317, 167, 335, 318, 122, 522, 392, 183, 329, 467, 472, 514, 149, 511, 282, 530, 178, 552, 55, 27, 281, 283, 361, 407, 189, 306, 99, 553, 60, 211, 197, 347, 527, 242, 467, 173, 3, 547, 516, 26, 308, 448, 61, 44, 423, 478, 357, 274, 255, 387, 168, 313, 166, 350, 172, 380, 405, 124, 383, 182, 219, 137, 153, 118, 424, 185, 240, 80, 302, 97, 433, 28, 166, 219, 502, 76, 485, 453, 507, 276, 18, 127, 122, 297, 270, 434, 128, 66, 506, 105, 270, 374, 555, 397, 181, 473, 192, 359, 223, 69, 162, 231, 547, 406, 561, 32, 421, 284, 515, 50, 348, 49, 18, 473, 195, 461, 42, 300, 356, 419, 105, 351, 289, 426, 60, 67, 52, 298, 244, 269, 259, 554, 80, 547, 249, 108, 373, 401, 514, 226, 333, 83, 135, 37, 89, 544, 362, 252, 85, 504, 169, 537, 17, 390, 111, 244, 53, 327, 203, 234, 282, 98, 325, 9, 484, 378, 87, 252, 75, 532, 205, 543, 77, 524, 309, 422, 179, 176, 166, 537, 177, 225, 314, 469, 364, 478, 449, 30, 18, 387, 142, 353, 332, 479, 199, 96, 331, 93, 24, 300, 182, 557, 408, 180, 316, 461, 455, 50, 471, 317, 288, 470, 354, 269, 88, 303, 53, 125, 331, 5, 280, 398, 307, 357, 174, 124, 476, 169, 4, 386, 138, 258, 75, 4, 113, 499, 346, 172, 356, 535, 530, 175, 94, 217, 351, 201, 36, 133, 216, 262, 575, 160, 215, 77, 162, 27, 273, 300, 428, 111, 488, 474, 330, 469, 231, 438, 61, 473, 551, 35, 260, 522, 394, 508, 210, 448, 353, 508, 439, 15, 172, 17, 34, 347, 18, 439, 124, 413, 526, 21, 74, 345, 556, 157, 271, 121, 566, 163, 323, 560, 33, 410, 142, 425, 336, 572, 3, 468, 231, 450, 424, 10, 133, 331, 245, 131, 156, 529, 150, 376, 101, 415, 467, 46, 143, 195, 272, 143, 8, 392, 476, 75, 416, 20, 128, 404, 178, 141, 267, 454, 151, 308, 230, 237, 392, 570, 174, 341, 220, 506, 446, 394, 182, 187, 106, 441, 93, 165, 330, 259, 335, 175, 184, 425, 140, 46, 166, 258, 106, 123, 125, 418, 355, 420, 384, 531, 521, 324, 301, 312, 13, 418, 382, 451, 73, 140, 269, 497, 196, 195, 165, 2, 121, 242, 51, 436, 68, 21, 530, 314, 528, 216, 315, 363, 5, 162, 318, 427, 26, 447, 245, 270, 254, 485, 302, 147, 340, 110, 228, 330, 127, 247, 142, 217, 479, 381, 476, 286, 155, 385, 378, 187, 180, 166, 193, 391, 560, 302, 21, 540, 212, 101, 141, 347, 423, 6, 282, 309, 547, 420, 563, 568, 256, 1, 298, 503, 74, 45, 468, 291, 133, 137, 220, 82, 575, 277, 267, 263, 454, 566, 34, 397, 445, 64, 504, 75, 6, 344, 331, 381, 273, 147, 354, 174, 181, 254, 11, 326, 302, 482, 272, 448, 200, 91, 283, 237, 554, 566, 324, 85, 287, 502, 547, 321, 182, 332, 60, 576, 242, 471, 490, 129, 332, 309, 455, 377, 511, 25, 122, 509, 510, 359, 340, 541, 379, 271, 412, 243, 166, 381, 345, 148, 15, 496, 98, 242, 137, 62, 42, 81, 283, 58, 241, 544, 335, 463, 478, 248, 17, 225, 76, 547, 74, 40, 97, 380, 429, 536, 259, 122, 205, 42, 66, 417, 498, 63, 298, 48, 366, 301, 216, 510, 240, 49, 172, 136, 152, 262, 107, 453, 572, 225, 184, 478, 94, 245, 272, 87, 345, 474, 392, 479, 250, 508, 500, 219, 260, 470, 512, 18, 564, 533, 162, 503, 169, 312, 54, 409, 520, 131, 155, 314, 440, 558, 231, 48, 373, 339, 155, 323, 476, 243, 18, 22, 541, 105, 531, 353, 205, 326, 88, 565, 427, 529, 238, 120, 386, 147, 130, 147, 375, 515, 395, 340, 536, 319, 191, 508, 510, 24, 115, 569, 57, 417, 285, 376, 26, 384, 462, 415, 237, 69, 532, 103, 501, 4, 288, 506, 64, 478, 18, 351, 237, 566, 228, 537, 49, 154, 445, 268, 563, 396, 508, 27, 277, 477, 225, 388, 460, 343, 223, 537, 40, 465, 311, 91, 142, 98, 365, 318, 22, 356, 175, 314, 513, 257, 471, 473, 559, 351, 89, 553, 165, 371, 119, 132, 201, 429, 124, 125, 47, 45, 540, 358, 180, 423, 350, 435, 525, 14, 67, 322, 345, 465, 264, 461, 53, 347, 102, 546, 327, 310, 58, 287, 151, 136, 448, 176, 554, 444, 160, 95, 89, 36, 65, 332, 409, 285, 187, 210, 190, 360, 302, 332, 486, 49, 316, 19, 278, 378, 327, 282, 424, 141, 159, 538, 223, 266, 103, 87, 535, 112, 427, 363, 382, 53, 22, 500, 527, 66, 520, 62, 244, 291, 289, 285, 492, 408, 416, 210, 524, 422, 545, 217, 13, 59, 172, 15, 146, 514, 463, 41, 213, 177, 482, 137, 320, 194, 295, 371, 449, 535, 280, 161, 19, 522, 4, 19, 547, 323, 444, 121, 168, 302, 490, 74, 560, 227, 266, 383, 53, 123, 557, 4, 371, 330, 301, 57, 406, 234, 329, 151, 485, 235, 240, 319, 155, 207, 11, 305, 253, 359, 231, 310, 152, 132, 512, 443, 36, 123, 380, 545, 358, 95, 231, 164, 451, 120, 42, 87, 1, 397, 163, 7, 213, 570, 452, 139, 389, 171, 508, 449, 392, 306, 401, 110, 56, 162, 245, 5, 322, 537, 381, 134, 97, 292, 291, 9, 571] + linepos = positions[idxs] + fig, ax, lineplot = lines( + linepos, linewidth=1, color=eachindex(linepos), transparency=true, + colormap = [:red, :blue], + figure = (size = (5000, 5000),) + ) + fig +end + @reference_test "image scatter" begin scatter( 1:10, 1:10, RNG.rand(10, 10) .* 10, From 7d6b9f9e49abdb94a5fbe7d8caac4161be5bdb27 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 25 Apr 2024 20:55:07 +0200 Subject: [PATCH 34/37] more testing --- GLMakie/assets/shader/line_segment.geom | 2 ++ GLMakie/assets/shader/lines.frag | 11 ++++++++--- GLMakie/assets/shader/lines.geom | 11 +++++++---- ReferenceTests/src/tests/examples3d.jl | 8 ++++---- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/GLMakie/assets/shader/line_segment.geom b/GLMakie/assets/shader/line_segment.geom index fd32a3508bd..91218425015 100644 --- a/GLMakie/assets/shader/line_segment.geom +++ b/GLMakie/assets/shader/line_segment.geom @@ -33,6 +33,7 @@ flat out float f_cumulative_length; flat out ivec2 f_capmode; flat out vec4 f_linepoints; flat out vec4 f_miter_vecs; +flat out vec4 f_line_vec_normal; const float AA_RADIUS = 0.8; const float AA_THICKNESS = 2.0 * AA_RADIUS; @@ -85,6 +86,7 @@ void main(void) f_extrusion = vec2(0.5); // no joints needing extrusion f_linepoints = vec4(-1e12); f_miter_vecs = vec4(-1); + f_line_vec_normal = vec4(v1.xy, n1.xy); // constants f_color1 = g_color[0]; diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index dee98e53bbb..0ce99ed28fb 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -27,6 +27,7 @@ flat in float f_cumulative_length; flat in ivec2 f_capmode; flat in vec4 f_linepoints; flat in vec4 f_miter_vecs; +flat in vec4 f_line_vec_normal; {{pattern_type}} pattern; uniform float pattern_length; @@ -194,15 +195,18 @@ if (!debug) { // ^ | ^ ^ | ^ // 1------------2 // ^ | ^ ^ | ^ - sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); + // sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); + // sdf = max(sdf, abs(f_quad_sdf.z) - 0.25 * AA_RADIUS); + sdf = max(sdf, 0.5 * abs(dot(2 * gl_FragCoord.xy - f_linepoints.xy - f_linepoints.zw, f_line_vec_normal.zw)) - f_linewidth); + // sdf = max(sdf, abs(f_quad_sdf.z) - 1000.0 * f_linewidth); // inner truncation (AA for overlapping parts) // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) // and b is the (smoothly) cut of part just after discard triggers (i.e not visible) // 100.0x sdf makes the sdf much more sharply, avoiding overdraw in the center - sdf = max(sdf, min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0)); - sdf = max(sdf, min(f_quad_sdf.y + 1.0, 100.0 * discard_sdf2 - 1.0)); + sdf = max(sdf, min(f_quad_sdf.x + 1.0, 1000.0 * discard_sdf1 - 1.0)); + sdf = max(sdf, min(f_quad_sdf.y + 1.0, 1000.0 * discard_sdf2 - 1.0)); // pattern application sdf = max(sdf, get_pattern_sdf(pattern, uv)); @@ -224,6 +228,7 @@ if (!debug) { color.a *= f_alpha_weight; if (!fxaa) { + // color.a *= step(0.0, -sdf); color.a *= aastep(0.0, -sdf); } else { color.a *= step(0.0, -sdf); diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 500df08f7f7..47d69c2f70f 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -34,6 +34,7 @@ flat out float f_cumulative_length; flat out ivec2 f_capmode; flat out vec4 f_linepoints; flat out vec4 f_miter_vecs; +flat out vec4 f_line_vec_normal; out vec3 o_view_pos; out vec3 o_view_normal; @@ -284,9 +285,9 @@ void main(void) v2 = normalize(p3.xy - p2.xy); // determine the normal of each of the 3 segments (previous, current, next) - vec2 n0 = normal_vector(v0); - vec2 n1 = normal_vector(v1); - vec2 n2 = normal_vector(v2); + vec2 n0 = normalize(normal_vector(v0)); + vec2 n1 = normalize(normal_vector(v1)); + vec2 n2 = normalize(normal_vector(v2)); // Miter normals (normal of truncated edge / vector to sharp corner) // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the @@ -427,6 +428,8 @@ void main(void) isvalid[3] ? joinstyle : linecap ); + f_line_vec_normal = vec4(v1.xy, n1.xy); + // Generate interpolated/varying outputs: LineVertex vertex; @@ -470,7 +473,7 @@ void main(void) // sdf of this segment vertex.quad_sdf.x = dot(VP1, -v1.xy); vertex.quad_sdf.y = dot(VP2, v1.xy); - vertex.quad_sdf.z = dot(VP1, n1); + vertex.quad_sdf.z = 0.5 * dot(VP1 + VP2, n1); // sdf for creating a flat cap on truncated joints // (sign(dot(...)) detects if line bends left or right) diff --git a/ReferenceTests/src/tests/examples3d.jl b/ReferenceTests/src/tests/examples3d.jl index e63b7b42bdc..8abd8e64666 100644 --- a/ReferenceTests/src/tests/examples3d.jl +++ b/ReferenceTests/src/tests/examples3d.jl @@ -370,7 +370,7 @@ end linepos = view(positions, RNG.rand(1:length(positions), 1000)) println(linepos.indices) fig, ax, lineplot = lines( - linepos, linewidth=0.1, color=:black, transparency=true, + linepos, linewidth=1, color=:black, transparency=true, figure = (size = (5000, 5000),) ) scatter!( @@ -387,7 +387,7 @@ end idxs = [44, 4, 560, 214, 306, 185, 511, 186, 230, 375, 216, 494, 326, 38, 246, 129, 378, 560, 290, 427, 341, 361, 392, 198, 512, 396, 395, 5, 493, 325, 279, 198, 203, 75, 467, 240, 419, 167, 531, 509, 15, 533, 431, 542, 490, 271, 33, 11, 548, 443, 465, 119, 80, 339, 372, 380, 568, 430, 375, 258, 193, 80, 403, 311, 575, 470, 494, 41, 524, 233, 539, 561, 154, 199, 241, 145, 185, 264, 446, 213, 84, 252, 130, 103, 308, 403, 425, 286, 83, 563, 563, 317, 167, 335, 318, 122, 522, 392, 183, 329, 467, 472, 514, 149, 511, 282, 530, 178, 552, 55, 27, 281, 283, 361, 407, 189, 306, 99, 553, 60, 211, 197, 347, 527, 242, 467, 173, 3, 547, 516, 26, 308, 448, 61, 44, 423, 478, 357, 274, 255, 387, 168, 313, 166, 350, 172, 380, 405, 124, 383, 182, 219, 137, 153, 118, 424, 185, 240, 80, 302, 97, 433, 28, 166, 219, 502, 76, 485, 453, 507, 276, 18, 127, 122, 297, 270, 434, 128, 66, 506, 105, 270, 374, 555, 397, 181, 473, 192, 359, 223, 69, 162, 231, 547, 406, 561, 32, 421, 284, 515, 50, 348, 49, 18, 473, 195, 461, 42, 300, 356, 419, 105, 351, 289, 426, 60, 67, 52, 298, 244, 269, 259, 554, 80, 547, 249, 108, 373, 401, 514, 226, 333, 83, 135, 37, 89, 544, 362, 252, 85, 504, 169, 537, 17, 390, 111, 244, 53, 327, 203, 234, 282, 98, 325, 9, 484, 378, 87, 252, 75, 532, 205, 543, 77, 524, 309, 422, 179, 176, 166, 537, 177, 225, 314, 469, 364, 478, 449, 30, 18, 387, 142, 353, 332, 479, 199, 96, 331, 93, 24, 300, 182, 557, 408, 180, 316, 461, 455, 50, 471, 317, 288, 470, 354, 269, 88, 303, 53, 125, 331, 5, 280, 398, 307, 357, 174, 124, 476, 169, 4, 386, 138, 258, 75, 4, 113, 499, 346, 172, 356, 535, 530, 175, 94, 217, 351, 201, 36, 133, 216, 262, 575, 160, 215, 77, 162, 27, 273, 300, 428, 111, 488, 474, 330, 469, 231, 438, 61, 473, 551, 35, 260, 522, 394, 508, 210, 448, 353, 508, 439, 15, 172, 17, 34, 347, 18, 439, 124, 413, 526, 21, 74, 345, 556, 157, 271, 121, 566, 163, 323, 560, 33, 410, 142, 425, 336, 572, 3, 468, 231, 450, 424, 10, 133, 331, 245, 131, 156, 529, 150, 376, 101, 415, 467, 46, 143, 195, 272, 143, 8, 392, 476, 75, 416, 20, 128, 404, 178, 141, 267, 454, 151, 308, 230, 237, 392, 570, 174, 341, 220, 506, 446, 394, 182, 187, 106, 441, 93, 165, 330, 259, 335, 175, 184, 425, 140, 46, 166, 258, 106, 123, 125, 418, 355, 420, 384, 531, 521, 324, 301, 312, 13, 418, 382, 451, 73, 140, 269, 497, 196, 195, 165, 2, 121, 242, 51, 436, 68, 21, 530, 314, 528, 216, 315, 363, 5, 162, 318, 427, 26, 447, 245, 270, 254, 485, 302, 147, 340, 110, 228, 330, 127, 247, 142, 217, 479, 381, 476, 286, 155, 385, 378, 187, 180, 166, 193, 391, 560, 302, 21, 540, 212, 101, 141, 347, 423, 6, 282, 309, 547, 420, 563, 568, 256, 1, 298, 503, 74, 45, 468, 291, 133, 137, 220, 82, 575, 277, 267, 263, 454, 566, 34, 397, 445, 64, 504, 75, 6, 344, 331, 381, 273, 147, 354, 174, 181, 254, 11, 326, 302, 482, 272, 448, 200, 91, 283, 237, 554, 566, 324, 85, 287, 502, 547, 321, 182, 332, 60, 576, 242, 471, 490, 129, 332, 309, 455, 377, 511, 25, 122, 509, 510, 359, 340, 541, 379, 271, 412, 243, 166, 381, 345, 148, 15, 496, 98, 242, 137, 62, 42, 81, 283, 58, 241, 544, 335, 463, 478, 248, 17, 225, 76, 547, 74, 40, 97, 380, 429, 536, 259, 122, 205, 42, 66, 417, 498, 63, 298, 48, 366, 301, 216, 510, 240, 49, 172, 136, 152, 262, 107, 453, 572, 225, 184, 478, 94, 245, 272, 87, 345, 474, 392, 479, 250, 508, 500, 219, 260, 470, 512, 18, 564, 533, 162, 503, 169, 312, 54, 409, 520, 131, 155, 314, 440, 558, 231, 48, 373, 339, 155, 323, 476, 243, 18, 22, 541, 105, 531, 353, 205, 326, 88, 565, 427, 529, 238, 120, 386, 147, 130, 147, 375, 515, 395, 340, 536, 319, 191, 508, 510, 24, 115, 569, 57, 417, 285, 376, 26, 384, 462, 415, 237, 69, 532, 103, 501, 4, 288, 506, 64, 478, 18, 351, 237, 566, 228, 537, 49, 154, 445, 268, 563, 396, 508, 27, 277, 477, 225, 388, 460, 343, 223, 537, 40, 465, 311, 91, 142, 98, 365, 318, 22, 356, 175, 314, 513, 257, 471, 473, 559, 351, 89, 553, 165, 371, 119, 132, 201, 429, 124, 125, 47, 45, 540, 358, 180, 423, 350, 435, 525, 14, 67, 322, 345, 465, 264, 461, 53, 347, 102, 546, 327, 310, 58, 287, 151, 136, 448, 176, 554, 444, 160, 95, 89, 36, 65, 332, 409, 285, 187, 210, 190, 360, 302, 332, 486, 49, 316, 19, 278, 378, 327, 282, 424, 141, 159, 538, 223, 266, 103, 87, 535, 112, 427, 363, 382, 53, 22, 500, 527, 66, 520, 62, 244, 291, 289, 285, 492, 408, 416, 210, 524, 422, 545, 217, 13, 59, 172, 15, 146, 514, 463, 41, 213, 177, 482, 137, 320, 194, 295, 371, 449, 535, 280, 161, 19, 522, 4, 19, 547, 323, 444, 121, 168, 302, 490, 74, 560, 227, 266, 383, 53, 123, 557, 4, 371, 330, 301, 57, 406, 234, 329, 151, 485, 235, 240, 319, 155, 207, 11, 305, 253, 359, 231, 310, 152, 132, 512, 443, 36, 123, 380, 545, 358, 95, 231, 164, 451, 120, 42, 87, 1, 397, 163, 7, 213, 570, 452, 139, 389, 171, 508, 449, 392, 306, 401, 110, 56, 162, 245, 5, 322, 537, 381, 134, 97, 292, 291, 9, 571] linepos = positions[idxs] fig, ax, lineplot = lines( - linepos, linewidth=1, color=:black, transparency=true, + linepos, linewidth=3, color=:black, transparency=true, figure = (size = (5000, 5000),) ) fig @@ -399,10 +399,10 @@ end idxs = [44, 4, 560, 214, 306, 185, 511, 186, 230, 375, 216, 494, 326, 38, 246, 129, 378, 560, 290, 427, 341, 361, 392, 198, 512, 396, 395, 5, 493, 325, 279, 198, 203, 75, 467, 240, 419, 167, 531, 509, 15, 533, 431, 542, 490, 271, 33, 11, 548, 443, 465, 119, 80, 339, 372, 380, 568, 430, 375, 258, 193, 80, 403, 311, 575, 470, 494, 41, 524, 233, 539, 561, 154, 199, 241, 145, 185, 264, 446, 213, 84, 252, 130, 103, 308, 403, 425, 286, 83, 563, 563, 317, 167, 335, 318, 122, 522, 392, 183, 329, 467, 472, 514, 149, 511, 282, 530, 178, 552, 55, 27, 281, 283, 361, 407, 189, 306, 99, 553, 60, 211, 197, 347, 527, 242, 467, 173, 3, 547, 516, 26, 308, 448, 61, 44, 423, 478, 357, 274, 255, 387, 168, 313, 166, 350, 172, 380, 405, 124, 383, 182, 219, 137, 153, 118, 424, 185, 240, 80, 302, 97, 433, 28, 166, 219, 502, 76, 485, 453, 507, 276, 18, 127, 122, 297, 270, 434, 128, 66, 506, 105, 270, 374, 555, 397, 181, 473, 192, 359, 223, 69, 162, 231, 547, 406, 561, 32, 421, 284, 515, 50, 348, 49, 18, 473, 195, 461, 42, 300, 356, 419, 105, 351, 289, 426, 60, 67, 52, 298, 244, 269, 259, 554, 80, 547, 249, 108, 373, 401, 514, 226, 333, 83, 135, 37, 89, 544, 362, 252, 85, 504, 169, 537, 17, 390, 111, 244, 53, 327, 203, 234, 282, 98, 325, 9, 484, 378, 87, 252, 75, 532, 205, 543, 77, 524, 309, 422, 179, 176, 166, 537, 177, 225, 314, 469, 364, 478, 449, 30, 18, 387, 142, 353, 332, 479, 199, 96, 331, 93, 24, 300, 182, 557, 408, 180, 316, 461, 455, 50, 471, 317, 288, 470, 354, 269, 88, 303, 53, 125, 331, 5, 280, 398, 307, 357, 174, 124, 476, 169, 4, 386, 138, 258, 75, 4, 113, 499, 346, 172, 356, 535, 530, 175, 94, 217, 351, 201, 36, 133, 216, 262, 575, 160, 215, 77, 162, 27, 273, 300, 428, 111, 488, 474, 330, 469, 231, 438, 61, 473, 551, 35, 260, 522, 394, 508, 210, 448, 353, 508, 439, 15, 172, 17, 34, 347, 18, 439, 124, 413, 526, 21, 74, 345, 556, 157, 271, 121, 566, 163, 323, 560, 33, 410, 142, 425, 336, 572, 3, 468, 231, 450, 424, 10, 133, 331, 245, 131, 156, 529, 150, 376, 101, 415, 467, 46, 143, 195, 272, 143, 8, 392, 476, 75, 416, 20, 128, 404, 178, 141, 267, 454, 151, 308, 230, 237, 392, 570, 174, 341, 220, 506, 446, 394, 182, 187, 106, 441, 93, 165, 330, 259, 335, 175, 184, 425, 140, 46, 166, 258, 106, 123, 125, 418, 355, 420, 384, 531, 521, 324, 301, 312, 13, 418, 382, 451, 73, 140, 269, 497, 196, 195, 165, 2, 121, 242, 51, 436, 68, 21, 530, 314, 528, 216, 315, 363, 5, 162, 318, 427, 26, 447, 245, 270, 254, 485, 302, 147, 340, 110, 228, 330, 127, 247, 142, 217, 479, 381, 476, 286, 155, 385, 378, 187, 180, 166, 193, 391, 560, 302, 21, 540, 212, 101, 141, 347, 423, 6, 282, 309, 547, 420, 563, 568, 256, 1, 298, 503, 74, 45, 468, 291, 133, 137, 220, 82, 575, 277, 267, 263, 454, 566, 34, 397, 445, 64, 504, 75, 6, 344, 331, 381, 273, 147, 354, 174, 181, 254, 11, 326, 302, 482, 272, 448, 200, 91, 283, 237, 554, 566, 324, 85, 287, 502, 547, 321, 182, 332, 60, 576, 242, 471, 490, 129, 332, 309, 455, 377, 511, 25, 122, 509, 510, 359, 340, 541, 379, 271, 412, 243, 166, 381, 345, 148, 15, 496, 98, 242, 137, 62, 42, 81, 283, 58, 241, 544, 335, 463, 478, 248, 17, 225, 76, 547, 74, 40, 97, 380, 429, 536, 259, 122, 205, 42, 66, 417, 498, 63, 298, 48, 366, 301, 216, 510, 240, 49, 172, 136, 152, 262, 107, 453, 572, 225, 184, 478, 94, 245, 272, 87, 345, 474, 392, 479, 250, 508, 500, 219, 260, 470, 512, 18, 564, 533, 162, 503, 169, 312, 54, 409, 520, 131, 155, 314, 440, 558, 231, 48, 373, 339, 155, 323, 476, 243, 18, 22, 541, 105, 531, 353, 205, 326, 88, 565, 427, 529, 238, 120, 386, 147, 130, 147, 375, 515, 395, 340, 536, 319, 191, 508, 510, 24, 115, 569, 57, 417, 285, 376, 26, 384, 462, 415, 237, 69, 532, 103, 501, 4, 288, 506, 64, 478, 18, 351, 237, 566, 228, 537, 49, 154, 445, 268, 563, 396, 508, 27, 277, 477, 225, 388, 460, 343, 223, 537, 40, 465, 311, 91, 142, 98, 365, 318, 22, 356, 175, 314, 513, 257, 471, 473, 559, 351, 89, 553, 165, 371, 119, 132, 201, 429, 124, 125, 47, 45, 540, 358, 180, 423, 350, 435, 525, 14, 67, 322, 345, 465, 264, 461, 53, 347, 102, 546, 327, 310, 58, 287, 151, 136, 448, 176, 554, 444, 160, 95, 89, 36, 65, 332, 409, 285, 187, 210, 190, 360, 302, 332, 486, 49, 316, 19, 278, 378, 327, 282, 424, 141, 159, 538, 223, 266, 103, 87, 535, 112, 427, 363, 382, 53, 22, 500, 527, 66, 520, 62, 244, 291, 289, 285, 492, 408, 416, 210, 524, 422, 545, 217, 13, 59, 172, 15, 146, 514, 463, 41, 213, 177, 482, 137, 320, 194, 295, 371, 449, 535, 280, 161, 19, 522, 4, 19, 547, 323, 444, 121, 168, 302, 490, 74, 560, 227, 266, 383, 53, 123, 557, 4, 371, 330, 301, 57, 406, 234, 329, 151, 485, 235, 240, 319, 155, 207, 11, 305, 253, 359, 231, 310, 152, 132, 512, 443, 36, 123, 380, 545, 358, 95, 231, 164, 451, 120, 42, 87, 1, 397, 163, 7, 213, 570, 452, 139, 389, 171, 508, 449, 392, 306, 401, 110, 56, 162, 245, 5, 322, 537, 381, 134, 97, 292, 291, 9, 571] linepos = positions[idxs] fig, ax, lineplot = lines( - linepos, linewidth=1, color=eachindex(linepos), transparency=true, - colormap = [:red, :blue], + linepos, linewidth=1, transparency=true, figure = (size = (5000, 5000),) ) + lineplot.attributes[:debug] = true fig end From 34b53ce379edee22ecd0f359b27d51861aefefa0 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 25 Apr 2024 21:31:28 +0200 Subject: [PATCH 35/37] more testing --- GLMakie/assets/shader/lines.frag | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index 0ce99ed28fb..64253cb4a31 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -158,7 +158,7 @@ if (!debug) { (f_quad_sdf.y > 0.0 && discard_sdf2 >= 0.0)) discard; - float sdf; + float sdf = -1e12; // f_quad_sdf.x includes everything from p1 in p2-p1 direction, i.e. > // f_quad_sdf.y includes everything from p2 in p1-p2 direction, i.e. < @@ -195,9 +195,9 @@ if (!debug) { // ^ | ^ ^ | ^ // 1------------2 // ^ | ^ ^ | ^ - // sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); + sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); // sdf = max(sdf, abs(f_quad_sdf.z) - 0.25 * AA_RADIUS); - sdf = max(sdf, 0.5 * abs(dot(2 * gl_FragCoord.xy - f_linepoints.xy - f_linepoints.zw, f_line_vec_normal.zw)) - f_linewidth); + // sdf = max(sdf, 0.5 * abs(dot(2 * gl_FragCoord.xy - f_linepoints.xy - f_linepoints.zw, f_line_vec_normal.zw)) - f_linewidth); // sdf = max(sdf, abs(f_quad_sdf.z) - 1000.0 * f_linewidth); // inner truncation (AA for overlapping parts) @@ -243,8 +243,28 @@ if (!debug) { color.rgb += (2 * mod(f_id.y, 2) - 1) * 0.1; // mark "outside" define by quad_sdf in black - float sdf = max(f_quad_sdf.x - f_extrusion.x, f_quad_sdf.y - f_extrusion.y); + // float sdf = max(f_quad_sdf.x - f_extrusion.x, f_quad_sdf.y - f_extrusion.y); + // sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); + + float sdf = -1e12; + if (f_capmode.x == ROUND) { + sdf = min(sqrt(f_quad_sdf.x * f_quad_sdf.x + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.x); + } else if (f_capmode.x == SQUARE) { + sdf = f_quad_sdf.x - f_linewidth; + } else { // miter or bevel joint or :butt cap + sdf = f_quad_sdf.x - f_extrusion.x; + } + if (f_capmode.y == ROUND) { // rounded joint or cap + sdf = max(sdf, + min(sqrt(f_quad_sdf.y * f_quad_sdf.y + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.y) + ); + } else if (f_capmode.y == SQUARE) { // :square cap + sdf = max(sdf, f_quad_sdf.y - f_linewidth); + } else { // miter or bevel joint or :butt cap + sdf = max(sdf, f_quad_sdf.y - f_extrusion.y); + } sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); + color.rgb -= vec3(0.4) * step(0.0, sdf); // Mark discarded space in red/blue @@ -262,7 +282,7 @@ if (!debug) { color.b += 0.2; // Mark regions excluded via truncation in green - color.g += 0.5 * step(0.0, max(f_truncation.x, f_truncation.y)); + color.rb += 0.5 * step(0.0, max(f_truncation.x, f_truncation.y)); // and inner truncation as softer green if (min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0) > 0.0) From 5c37d3c2b6a9db1db998c00dd9852757bd2a7c96 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 25 Apr 2024 22:06:48 +0200 Subject: [PATCH 36/37] revert debugging --- GLMakie/assets/shader/line_segment.geom | 2 -- GLMakie/assets/shader/lines.frag | 35 ++++--------------------- GLMakie/assets/shader/lines.geom | 11 +++----- ReferenceTests/src/tests/examples3d.jl | 31 +--------------------- 4 files changed, 10 insertions(+), 69 deletions(-) diff --git a/GLMakie/assets/shader/line_segment.geom b/GLMakie/assets/shader/line_segment.geom index 91218425015..fd32a3508bd 100644 --- a/GLMakie/assets/shader/line_segment.geom +++ b/GLMakie/assets/shader/line_segment.geom @@ -33,7 +33,6 @@ flat out float f_cumulative_length; flat out ivec2 f_capmode; flat out vec4 f_linepoints; flat out vec4 f_miter_vecs; -flat out vec4 f_line_vec_normal; const float AA_RADIUS = 0.8; const float AA_THICKNESS = 2.0 * AA_RADIUS; @@ -86,7 +85,6 @@ void main(void) f_extrusion = vec2(0.5); // no joints needing extrusion f_linepoints = vec4(-1e12); f_miter_vecs = vec4(-1); - f_line_vec_normal = vec4(v1.xy, n1.xy); // constants f_color1 = g_color[0]; diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index 64253cb4a31..dee98e53bbb 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -27,7 +27,6 @@ flat in float f_cumulative_length; flat in ivec2 f_capmode; flat in vec4 f_linepoints; flat in vec4 f_miter_vecs; -flat in vec4 f_line_vec_normal; {{pattern_type}} pattern; uniform float pattern_length; @@ -158,7 +157,7 @@ if (!debug) { (f_quad_sdf.y > 0.0 && discard_sdf2 >= 0.0)) discard; - float sdf = -1e12; + float sdf; // f_quad_sdf.x includes everything from p1 in p2-p1 direction, i.e. > // f_quad_sdf.y includes everything from p2 in p1-p2 direction, i.e. < @@ -196,17 +195,14 @@ if (!debug) { // 1------------2 // ^ | ^ ^ | ^ sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); - // sdf = max(sdf, abs(f_quad_sdf.z) - 0.25 * AA_RADIUS); - // sdf = max(sdf, 0.5 * abs(dot(2 * gl_FragCoord.xy - f_linepoints.xy - f_linepoints.zw, f_line_vec_normal.zw)) - f_linewidth); - // sdf = max(sdf, abs(f_quad_sdf.z) - 1000.0 * f_linewidth); // inner truncation (AA for overlapping parts) // min(a, b) keeps what is inside a and b // where a is the smoothly cut of part just before discard triggers (i.e. visible) // and b is the (smoothly) cut of part just after discard triggers (i.e not visible) // 100.0x sdf makes the sdf much more sharply, avoiding overdraw in the center - sdf = max(sdf, min(f_quad_sdf.x + 1.0, 1000.0 * discard_sdf1 - 1.0)); - sdf = max(sdf, min(f_quad_sdf.y + 1.0, 1000.0 * discard_sdf2 - 1.0)); + sdf = max(sdf, min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0)); + sdf = max(sdf, min(f_quad_sdf.y + 1.0, 100.0 * discard_sdf2 - 1.0)); // pattern application sdf = max(sdf, get_pattern_sdf(pattern, uv)); @@ -228,7 +224,6 @@ if (!debug) { color.a *= f_alpha_weight; if (!fxaa) { - // color.a *= step(0.0, -sdf); color.a *= aastep(0.0, -sdf); } else { color.a *= step(0.0, -sdf); @@ -243,28 +238,8 @@ if (!debug) { color.rgb += (2 * mod(f_id.y, 2) - 1) * 0.1; // mark "outside" define by quad_sdf in black - // float sdf = max(f_quad_sdf.x - f_extrusion.x, f_quad_sdf.y - f_extrusion.y); - // sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); - - float sdf = -1e12; - if (f_capmode.x == ROUND) { - sdf = min(sqrt(f_quad_sdf.x * f_quad_sdf.x + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.x); - } else if (f_capmode.x == SQUARE) { - sdf = f_quad_sdf.x - f_linewidth; - } else { // miter or bevel joint or :butt cap - sdf = f_quad_sdf.x - f_extrusion.x; - } - if (f_capmode.y == ROUND) { // rounded joint or cap - sdf = max(sdf, - min(sqrt(f_quad_sdf.y * f_quad_sdf.y + f_quad_sdf.z * f_quad_sdf.z) - f_linewidth, f_quad_sdf.y) - ); - } else if (f_capmode.y == SQUARE) { // :square cap - sdf = max(sdf, f_quad_sdf.y - f_linewidth); - } else { // miter or bevel joint or :butt cap - sdf = max(sdf, f_quad_sdf.y - f_extrusion.y); - } + float sdf = max(f_quad_sdf.x - f_extrusion.x, f_quad_sdf.y - f_extrusion.y); sdf = max(sdf, abs(f_quad_sdf.z) - f_linewidth); - color.rgb -= vec3(0.4) * step(0.0, sdf); // Mark discarded space in red/blue @@ -282,7 +257,7 @@ if (!debug) { color.b += 0.2; // Mark regions excluded via truncation in green - color.rb += 0.5 * step(0.0, max(f_truncation.x, f_truncation.y)); + color.g += 0.5 * step(0.0, max(f_truncation.x, f_truncation.y)); // and inner truncation as softer green if (min(f_quad_sdf.x + 1.0, 100.0 * discard_sdf1 - 1.0) > 0.0) diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 47d69c2f70f..500df08f7f7 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -34,7 +34,6 @@ flat out float f_cumulative_length; flat out ivec2 f_capmode; flat out vec4 f_linepoints; flat out vec4 f_miter_vecs; -flat out vec4 f_line_vec_normal; out vec3 o_view_pos; out vec3 o_view_normal; @@ -285,9 +284,9 @@ void main(void) v2 = normalize(p3.xy - p2.xy); // determine the normal of each of the 3 segments (previous, current, next) - vec2 n0 = normalize(normal_vector(v0)); - vec2 n1 = normalize(normal_vector(v1)); - vec2 n2 = normalize(normal_vector(v2)); + vec2 n0 = normal_vector(v0); + vec2 n1 = normal_vector(v1); + vec2 n2 = normal_vector(v2); // Miter normals (normal of truncated edge / vector to sharp corner) // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the @@ -428,8 +427,6 @@ void main(void) isvalid[3] ? joinstyle : linecap ); - f_line_vec_normal = vec4(v1.xy, n1.xy); - // Generate interpolated/varying outputs: LineVertex vertex; @@ -473,7 +470,7 @@ void main(void) // sdf of this segment vertex.quad_sdf.x = dot(VP1, -v1.xy); vertex.quad_sdf.y = dot(VP2, v1.xy); - vertex.quad_sdf.z = 0.5 * dot(VP1 + VP2, n1); + vertex.quad_sdf.z = dot(VP1, n1); // sdf for creating a flat cap on truncated joints // (sign(dot(...)) detects if line bends left or right) diff --git a/ReferenceTests/src/tests/examples3d.jl b/ReferenceTests/src/tests/examples3d.jl index 8abd8e64666..890ddde407d 100644 --- a/ReferenceTests/src/tests/examples3d.jl +++ b/ReferenceTests/src/tests/examples3d.jl @@ -368,11 +368,7 @@ end large_sphere = Sphere(Point3f(0), 1f0) positions = decompose(Point3f, large_sphere) linepos = view(positions, RNG.rand(1:length(positions), 1000)) - println(linepos.indices) - fig, ax, lineplot = lines( - linepos, linewidth=1, color=:black, transparency=true, - figure = (size = (5000, 5000),) - ) + fig, ax, lineplot = lines(linepos, linewidth=0.1, color=:black, transparency=true) scatter!( ax, positions, markersize=10, strokewidth=2, strokecolor=:white, @@ -381,31 +377,6 @@ end fig end -@reference_test "Connected Sphere 2" begin - large_sphere = Sphere(Point3f(0), 1f0) - positions = decompose(Point3f, large_sphere) - idxs = [44, 4, 560, 214, 306, 185, 511, 186, 230, 375, 216, 494, 326, 38, 246, 129, 378, 560, 290, 427, 341, 361, 392, 198, 512, 396, 395, 5, 493, 325, 279, 198, 203, 75, 467, 240, 419, 167, 531, 509, 15, 533, 431, 542, 490, 271, 33, 11, 548, 443, 465, 119, 80, 339, 372, 380, 568, 430, 375, 258, 193, 80, 403, 311, 575, 470, 494, 41, 524, 233, 539, 561, 154, 199, 241, 145, 185, 264, 446, 213, 84, 252, 130, 103, 308, 403, 425, 286, 83, 563, 563, 317, 167, 335, 318, 122, 522, 392, 183, 329, 467, 472, 514, 149, 511, 282, 530, 178, 552, 55, 27, 281, 283, 361, 407, 189, 306, 99, 553, 60, 211, 197, 347, 527, 242, 467, 173, 3, 547, 516, 26, 308, 448, 61, 44, 423, 478, 357, 274, 255, 387, 168, 313, 166, 350, 172, 380, 405, 124, 383, 182, 219, 137, 153, 118, 424, 185, 240, 80, 302, 97, 433, 28, 166, 219, 502, 76, 485, 453, 507, 276, 18, 127, 122, 297, 270, 434, 128, 66, 506, 105, 270, 374, 555, 397, 181, 473, 192, 359, 223, 69, 162, 231, 547, 406, 561, 32, 421, 284, 515, 50, 348, 49, 18, 473, 195, 461, 42, 300, 356, 419, 105, 351, 289, 426, 60, 67, 52, 298, 244, 269, 259, 554, 80, 547, 249, 108, 373, 401, 514, 226, 333, 83, 135, 37, 89, 544, 362, 252, 85, 504, 169, 537, 17, 390, 111, 244, 53, 327, 203, 234, 282, 98, 325, 9, 484, 378, 87, 252, 75, 532, 205, 543, 77, 524, 309, 422, 179, 176, 166, 537, 177, 225, 314, 469, 364, 478, 449, 30, 18, 387, 142, 353, 332, 479, 199, 96, 331, 93, 24, 300, 182, 557, 408, 180, 316, 461, 455, 50, 471, 317, 288, 470, 354, 269, 88, 303, 53, 125, 331, 5, 280, 398, 307, 357, 174, 124, 476, 169, 4, 386, 138, 258, 75, 4, 113, 499, 346, 172, 356, 535, 530, 175, 94, 217, 351, 201, 36, 133, 216, 262, 575, 160, 215, 77, 162, 27, 273, 300, 428, 111, 488, 474, 330, 469, 231, 438, 61, 473, 551, 35, 260, 522, 394, 508, 210, 448, 353, 508, 439, 15, 172, 17, 34, 347, 18, 439, 124, 413, 526, 21, 74, 345, 556, 157, 271, 121, 566, 163, 323, 560, 33, 410, 142, 425, 336, 572, 3, 468, 231, 450, 424, 10, 133, 331, 245, 131, 156, 529, 150, 376, 101, 415, 467, 46, 143, 195, 272, 143, 8, 392, 476, 75, 416, 20, 128, 404, 178, 141, 267, 454, 151, 308, 230, 237, 392, 570, 174, 341, 220, 506, 446, 394, 182, 187, 106, 441, 93, 165, 330, 259, 335, 175, 184, 425, 140, 46, 166, 258, 106, 123, 125, 418, 355, 420, 384, 531, 521, 324, 301, 312, 13, 418, 382, 451, 73, 140, 269, 497, 196, 195, 165, 2, 121, 242, 51, 436, 68, 21, 530, 314, 528, 216, 315, 363, 5, 162, 318, 427, 26, 447, 245, 270, 254, 485, 302, 147, 340, 110, 228, 330, 127, 247, 142, 217, 479, 381, 476, 286, 155, 385, 378, 187, 180, 166, 193, 391, 560, 302, 21, 540, 212, 101, 141, 347, 423, 6, 282, 309, 547, 420, 563, 568, 256, 1, 298, 503, 74, 45, 468, 291, 133, 137, 220, 82, 575, 277, 267, 263, 454, 566, 34, 397, 445, 64, 504, 75, 6, 344, 331, 381, 273, 147, 354, 174, 181, 254, 11, 326, 302, 482, 272, 448, 200, 91, 283, 237, 554, 566, 324, 85, 287, 502, 547, 321, 182, 332, 60, 576, 242, 471, 490, 129, 332, 309, 455, 377, 511, 25, 122, 509, 510, 359, 340, 541, 379, 271, 412, 243, 166, 381, 345, 148, 15, 496, 98, 242, 137, 62, 42, 81, 283, 58, 241, 544, 335, 463, 478, 248, 17, 225, 76, 547, 74, 40, 97, 380, 429, 536, 259, 122, 205, 42, 66, 417, 498, 63, 298, 48, 366, 301, 216, 510, 240, 49, 172, 136, 152, 262, 107, 453, 572, 225, 184, 478, 94, 245, 272, 87, 345, 474, 392, 479, 250, 508, 500, 219, 260, 470, 512, 18, 564, 533, 162, 503, 169, 312, 54, 409, 520, 131, 155, 314, 440, 558, 231, 48, 373, 339, 155, 323, 476, 243, 18, 22, 541, 105, 531, 353, 205, 326, 88, 565, 427, 529, 238, 120, 386, 147, 130, 147, 375, 515, 395, 340, 536, 319, 191, 508, 510, 24, 115, 569, 57, 417, 285, 376, 26, 384, 462, 415, 237, 69, 532, 103, 501, 4, 288, 506, 64, 478, 18, 351, 237, 566, 228, 537, 49, 154, 445, 268, 563, 396, 508, 27, 277, 477, 225, 388, 460, 343, 223, 537, 40, 465, 311, 91, 142, 98, 365, 318, 22, 356, 175, 314, 513, 257, 471, 473, 559, 351, 89, 553, 165, 371, 119, 132, 201, 429, 124, 125, 47, 45, 540, 358, 180, 423, 350, 435, 525, 14, 67, 322, 345, 465, 264, 461, 53, 347, 102, 546, 327, 310, 58, 287, 151, 136, 448, 176, 554, 444, 160, 95, 89, 36, 65, 332, 409, 285, 187, 210, 190, 360, 302, 332, 486, 49, 316, 19, 278, 378, 327, 282, 424, 141, 159, 538, 223, 266, 103, 87, 535, 112, 427, 363, 382, 53, 22, 500, 527, 66, 520, 62, 244, 291, 289, 285, 492, 408, 416, 210, 524, 422, 545, 217, 13, 59, 172, 15, 146, 514, 463, 41, 213, 177, 482, 137, 320, 194, 295, 371, 449, 535, 280, 161, 19, 522, 4, 19, 547, 323, 444, 121, 168, 302, 490, 74, 560, 227, 266, 383, 53, 123, 557, 4, 371, 330, 301, 57, 406, 234, 329, 151, 485, 235, 240, 319, 155, 207, 11, 305, 253, 359, 231, 310, 152, 132, 512, 443, 36, 123, 380, 545, 358, 95, 231, 164, 451, 120, 42, 87, 1, 397, 163, 7, 213, 570, 452, 139, 389, 171, 508, 449, 392, 306, 401, 110, 56, 162, 245, 5, 322, 537, 381, 134, 97, 292, 291, 9, 571] - linepos = positions[idxs] - fig, ax, lineplot = lines( - linepos, linewidth=3, color=:black, transparency=true, - figure = (size = (5000, 5000),) - ) - fig -end - -@reference_test "Connected Sphere 3" begin - large_sphere = Sphere(Point3f(0), 1f0) - positions = decompose(Point3f, large_sphere) - idxs = [44, 4, 560, 214, 306, 185, 511, 186, 230, 375, 216, 494, 326, 38, 246, 129, 378, 560, 290, 427, 341, 361, 392, 198, 512, 396, 395, 5, 493, 325, 279, 198, 203, 75, 467, 240, 419, 167, 531, 509, 15, 533, 431, 542, 490, 271, 33, 11, 548, 443, 465, 119, 80, 339, 372, 380, 568, 430, 375, 258, 193, 80, 403, 311, 575, 470, 494, 41, 524, 233, 539, 561, 154, 199, 241, 145, 185, 264, 446, 213, 84, 252, 130, 103, 308, 403, 425, 286, 83, 563, 563, 317, 167, 335, 318, 122, 522, 392, 183, 329, 467, 472, 514, 149, 511, 282, 530, 178, 552, 55, 27, 281, 283, 361, 407, 189, 306, 99, 553, 60, 211, 197, 347, 527, 242, 467, 173, 3, 547, 516, 26, 308, 448, 61, 44, 423, 478, 357, 274, 255, 387, 168, 313, 166, 350, 172, 380, 405, 124, 383, 182, 219, 137, 153, 118, 424, 185, 240, 80, 302, 97, 433, 28, 166, 219, 502, 76, 485, 453, 507, 276, 18, 127, 122, 297, 270, 434, 128, 66, 506, 105, 270, 374, 555, 397, 181, 473, 192, 359, 223, 69, 162, 231, 547, 406, 561, 32, 421, 284, 515, 50, 348, 49, 18, 473, 195, 461, 42, 300, 356, 419, 105, 351, 289, 426, 60, 67, 52, 298, 244, 269, 259, 554, 80, 547, 249, 108, 373, 401, 514, 226, 333, 83, 135, 37, 89, 544, 362, 252, 85, 504, 169, 537, 17, 390, 111, 244, 53, 327, 203, 234, 282, 98, 325, 9, 484, 378, 87, 252, 75, 532, 205, 543, 77, 524, 309, 422, 179, 176, 166, 537, 177, 225, 314, 469, 364, 478, 449, 30, 18, 387, 142, 353, 332, 479, 199, 96, 331, 93, 24, 300, 182, 557, 408, 180, 316, 461, 455, 50, 471, 317, 288, 470, 354, 269, 88, 303, 53, 125, 331, 5, 280, 398, 307, 357, 174, 124, 476, 169, 4, 386, 138, 258, 75, 4, 113, 499, 346, 172, 356, 535, 530, 175, 94, 217, 351, 201, 36, 133, 216, 262, 575, 160, 215, 77, 162, 27, 273, 300, 428, 111, 488, 474, 330, 469, 231, 438, 61, 473, 551, 35, 260, 522, 394, 508, 210, 448, 353, 508, 439, 15, 172, 17, 34, 347, 18, 439, 124, 413, 526, 21, 74, 345, 556, 157, 271, 121, 566, 163, 323, 560, 33, 410, 142, 425, 336, 572, 3, 468, 231, 450, 424, 10, 133, 331, 245, 131, 156, 529, 150, 376, 101, 415, 467, 46, 143, 195, 272, 143, 8, 392, 476, 75, 416, 20, 128, 404, 178, 141, 267, 454, 151, 308, 230, 237, 392, 570, 174, 341, 220, 506, 446, 394, 182, 187, 106, 441, 93, 165, 330, 259, 335, 175, 184, 425, 140, 46, 166, 258, 106, 123, 125, 418, 355, 420, 384, 531, 521, 324, 301, 312, 13, 418, 382, 451, 73, 140, 269, 497, 196, 195, 165, 2, 121, 242, 51, 436, 68, 21, 530, 314, 528, 216, 315, 363, 5, 162, 318, 427, 26, 447, 245, 270, 254, 485, 302, 147, 340, 110, 228, 330, 127, 247, 142, 217, 479, 381, 476, 286, 155, 385, 378, 187, 180, 166, 193, 391, 560, 302, 21, 540, 212, 101, 141, 347, 423, 6, 282, 309, 547, 420, 563, 568, 256, 1, 298, 503, 74, 45, 468, 291, 133, 137, 220, 82, 575, 277, 267, 263, 454, 566, 34, 397, 445, 64, 504, 75, 6, 344, 331, 381, 273, 147, 354, 174, 181, 254, 11, 326, 302, 482, 272, 448, 200, 91, 283, 237, 554, 566, 324, 85, 287, 502, 547, 321, 182, 332, 60, 576, 242, 471, 490, 129, 332, 309, 455, 377, 511, 25, 122, 509, 510, 359, 340, 541, 379, 271, 412, 243, 166, 381, 345, 148, 15, 496, 98, 242, 137, 62, 42, 81, 283, 58, 241, 544, 335, 463, 478, 248, 17, 225, 76, 547, 74, 40, 97, 380, 429, 536, 259, 122, 205, 42, 66, 417, 498, 63, 298, 48, 366, 301, 216, 510, 240, 49, 172, 136, 152, 262, 107, 453, 572, 225, 184, 478, 94, 245, 272, 87, 345, 474, 392, 479, 250, 508, 500, 219, 260, 470, 512, 18, 564, 533, 162, 503, 169, 312, 54, 409, 520, 131, 155, 314, 440, 558, 231, 48, 373, 339, 155, 323, 476, 243, 18, 22, 541, 105, 531, 353, 205, 326, 88, 565, 427, 529, 238, 120, 386, 147, 130, 147, 375, 515, 395, 340, 536, 319, 191, 508, 510, 24, 115, 569, 57, 417, 285, 376, 26, 384, 462, 415, 237, 69, 532, 103, 501, 4, 288, 506, 64, 478, 18, 351, 237, 566, 228, 537, 49, 154, 445, 268, 563, 396, 508, 27, 277, 477, 225, 388, 460, 343, 223, 537, 40, 465, 311, 91, 142, 98, 365, 318, 22, 356, 175, 314, 513, 257, 471, 473, 559, 351, 89, 553, 165, 371, 119, 132, 201, 429, 124, 125, 47, 45, 540, 358, 180, 423, 350, 435, 525, 14, 67, 322, 345, 465, 264, 461, 53, 347, 102, 546, 327, 310, 58, 287, 151, 136, 448, 176, 554, 444, 160, 95, 89, 36, 65, 332, 409, 285, 187, 210, 190, 360, 302, 332, 486, 49, 316, 19, 278, 378, 327, 282, 424, 141, 159, 538, 223, 266, 103, 87, 535, 112, 427, 363, 382, 53, 22, 500, 527, 66, 520, 62, 244, 291, 289, 285, 492, 408, 416, 210, 524, 422, 545, 217, 13, 59, 172, 15, 146, 514, 463, 41, 213, 177, 482, 137, 320, 194, 295, 371, 449, 535, 280, 161, 19, 522, 4, 19, 547, 323, 444, 121, 168, 302, 490, 74, 560, 227, 266, 383, 53, 123, 557, 4, 371, 330, 301, 57, 406, 234, 329, 151, 485, 235, 240, 319, 155, 207, 11, 305, 253, 359, 231, 310, 152, 132, 512, 443, 36, 123, 380, 545, 358, 95, 231, 164, 451, 120, 42, 87, 1, 397, 163, 7, 213, 570, 452, 139, 389, 171, 508, 449, 392, 306, 401, 110, 56, 162, 245, 5, 322, 537, 381, 134, 97, 292, 291, 9, 571] - linepos = positions[idxs] - fig, ax, lineplot = lines( - linepos, linewidth=1, transparency=true, - figure = (size = (5000, 5000),) - ) - lineplot.attributes[:debug] = true - fig -end - @reference_test "image scatter" begin scatter( 1:10, 1:10, RNG.rand(10, 10) .* 10, From 6cfa9f71608ce4ae3c4236c83c61462de54662e9 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 26 Apr 2024 00:48:17 +0200 Subject: [PATCH 37/37] fix test? --- GLMakie/assets/shader/lines.geom | 7 +++++-- WGLMakie/src/Lines.js | 7 +++++-- WGLMakie/src/wglmakie.bundled.js | 7 +++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 500df08f7f7..f6c198f728d 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -84,6 +84,7 @@ void emit_vertex(LineVertex vertex) { vec2 normal_vector(in vec2 v) { return vec2(-v.y, v.x); } vec2 normal_vector(in vec3 v) { return vec2(-v.y, v.x); } +float sign_no_zero(float value) { return value >= 0.0 ? 1.0 : -1.0; } //////////////////////////////////////////////////////////////////////////////// @@ -292,8 +293,10 @@ void main(void) // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); - vec2 miter_n1 = miter.x < -0.0 ? sign(dot(v0.xy, n1)) * normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = miter.y < -0.0 ? sign(dot(v1.xy, n2)) * normalize(v1.xy - v2.xy) : normalize(n1 + n2); + vec2 miter_n1 = miter.x < 0.0 ? + sign_no_zero(dot(v0.xy, n1)) * normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < 0.0 ? + sign_no_zero(dot(v1.xy, n2)) * normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index 39e9d5587d8..03850e5701a 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -333,6 +333,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 normal_vector(in vec2 v) { return vec2(-v.y, v.x); } vec2 normal_vector(in vec3 v) { return vec2(-v.y, v.x); } + float sign_no_zero(float value) { return value >= 0.0 ? 1.0 : -1.0; } //////////////////////////////////////////////////////////////////////// @@ -430,8 +431,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); - vec2 miter_n1 = miter.x < -0.0 ? sign(dot(v0.xy, n1)) *normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = miter.y < -0.0 ? sign(dot(v1.xy, n2)) *normalize(v1.xy - v2.xy) : normalize(n1 + n2); + vec2 miter_n1 = miter.x < -0.0 ? + sign_no_zero(dot(v0.xy, n1)) * normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < -0.0 ? + sign_no_zero(dot(v1.xy, n2)) * normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 63357f21399..ae5a10200b0 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21610,6 +21610,7 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { vec2 normal_vector(in vec2 v) { return vec2(-v.y, v.x); } vec2 normal_vector(in vec3 v) { return vec2(-v.y, v.x); } + float sign_no_zero(float value) { return value >= 0.0 ? 1.0 : -1.0; } //////////////////////////////////////////////////////////////////////// @@ -21707,8 +21708,10 @@ function lines_vertex_shader(uniforms, attributes, is_linesegments) { // Note: n0 + n1 = vec(0) for a 180° change in direction. +-(v0 - v1) is the // same direction, but becomes vec(0) at 0°, so we can use it instead vec2 miter = vec2(dot(v0, v1.xy), dot(v1.xy, v2)); - vec2 miter_n1 = miter.x < -0.0 ? sign(dot(v0.xy, n1)) *normalize(v0.xy - v1.xy) : normalize(n0 + n1); - vec2 miter_n2 = miter.y < -0.0 ? sign(dot(v1.xy, n2)) *normalize(v1.xy - v2.xy) : normalize(n1 + n2); + vec2 miter_n1 = miter.x < -0.0 ? + sign_no_zero(dot(v0.xy, n1)) * normalize(v0.xy - v1.xy) : normalize(n0 + n1); + vec2 miter_n2 = miter.y < -0.0 ? + sign_no_zero(dot(v1.xy, n2)) * normalize(v1.xy - v2.xy) : normalize(n1 + n2); // Are we truncating the joint based on miter limit or joinstyle? // bevel / always truncate doesn't work with v1 == v2 (v0) so we use allow