diff --git a/GLMakie/assets/shader/line_segment.geom b/GLMakie/assets/shader/line_segment.geom index cc879972e0a..7fa912ac193 100644 --- a/GLMakie/assets/shader/line_segment.geom +++ b/GLMakie/assets/shader/line_segment.geom @@ -16,12 +16,13 @@ uniform int linecap; in {{stripped_color_type}} g_color[]; in uvec2 g_id[]; in float g_thickness[]; -in float g_clip_distance[][8]; out vec3 f_quad_sdf; out vec2 f_truncation; out float f_linestart; out float f_linelength; +out vec3 o_view_pos; +out vec3 o_view_normal; flat out float f_linewidth; flat out vec4 f_pattern_overwrite; @@ -34,11 +35,40 @@ flat out float f_cumulative_length; flat out ivec2 f_capmode; flat out vec4 f_linepoints; flat out vec4 f_miter_vecs; -out float gl_ClipDistance[8]; const float AA_RADIUS = 0.8; const float AA_THICKNESS = 2.0 * AA_RADIUS; +uniform mat4 projectionview; +uniform int _num_clip_planes; +uniform vec4 clip_planes[8]; + +bool process_clip_planes(inout vec4 p1, inout vec4 p2) +{ + float d1, d2; + for (int i = 0; i < _num_clip_planes; i++) { + // distance from clip planes with negative clipped + d1 = dot(p1.xyz, clip_planes[i].xyz) - clip_planes[i].w; + d2 = dot(p2.xyz, clip_planes[i].xyz) - clip_planes[i].w; + + // both outside - clip everything + if (d1 < 0.0 && d2 < 0.0) { + p2 = p1; + return true; + } + + // one outside - shorten segment + else if (d1 < 0.0) + // solve 0 = m * t + b = (d2 - d1) * t + d1 with t in (0, 1) + p1 = p1 - d1 * (p2 - p1) / (d2 - d1); + else if (d2 < 0.0) + p2 = p2 - d2 * (p1 - p2) / (d1 - d2); + } + + return false; +} + + vec3 screen_space(vec4 vertex) { return vec3((0.5 * vertex.xy / vertex.w + 0.5) * resolution, vertex.z / vertex.w); } @@ -46,9 +76,6 @@ vec3 screen_space(vec4 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); } -out vec3 o_view_pos; -out vec3 o_view_normal; - void main(void) { o_view_pos = vec3(0); @@ -62,15 +89,32 @@ void main(void) // get start and end point of line segment // restrict to visible area (see lines.geom) vec3 p1, p2; + vec2 clip_factor = vec2(0); { vec4 _p1 = gl_in[0].gl_Position, _p2 = gl_in[1].gl_Position; + + // Shorten segments to fit clip planes + // returns true if segments are fully clipped + if (process_clip_planes(_p1, _p2)) + return; + + // remaining world -> clip projection + _p1 = projectionview * _p1; + _p2 = projectionview * _p2; + + // Handle near/far clip planes vec4 v1 = _p2 - _p1; - if (_p1.w < 0.0) - _p1 = _p1 + (-_p1.w - _p1.z) / (v1.z + v1.w) * v1; - if (_p2.w < 0.0) - _p2 = _p2 + (-_p2.w - _p2.z) / (v1.z + v1.w) * v1; + if (_p1.w < 0.0) { + clip_factor.x = (-_p1.w - _p1.z) / (v1.z + v1.w); + _p1 = _p1 + clip_factor.x * v1; + } + if (_p2.w < 0.0) { + clip_factor.y = (-_p2.w - _p2.z) / (v1.z + v1.w); + _p2 = _p2 + clip_factor.y * v1; + } + // clip -> pixel/screen projection p1 = screen_space(_p1); p2 = screen_space(_p2); } @@ -110,9 +154,6 @@ void main(void) f_linewidth = halfwidth; f_id = g_id[x]; - for (int i = 0; i < 8; i++) - gl_ClipDistance[i] = g_clip_distance[x][i]; - for (int y = 0; y < 2; y++) { // Get offset in y direction & compute vertex position float n_offset = (2 * y - 1) * (halfwidth + AA_THICKNESS); diff --git a/GLMakie/assets/shader/line_segment.vert b/GLMakie/assets/shader/line_segment.vert index fdb58161406..159fd9db16a 100644 --- a/GLMakie/assets/shader/line_segment.vert +++ b/GLMakie/assets/shader/line_segment.vert @@ -22,30 +22,10 @@ out float g_thickness; vec4 to_vec4(vec3 v){return vec4(v, 1);} vec4 to_vec4(vec2 v){return vec4(v, 0, 1);} -uniform int num_clip_planes; -uniform vec4 clip_planes[8]; -out float g_clip_distance[8]; - -void process_clip_planes(vec3 world_pos) -{ - // distance = dot(world_pos - plane.point, plane.normal) - // precalculated: dot(plane.point, plane.normal) -> plane.w - for (int i = 0; i < num_clip_planes; i++) - g_clip_distance[i] = dot(world_pos, clip_planes[i].xyz) - clip_planes[i].w; - - // TODO: can be skipped? - for (int i = num_clip_planes; i < 8; i++) - g_clip_distance[i] = 1.0; -} - - void main() { g_id = uvec2(objectid, gl_VertexID + 1); g_color = color; g_thickness = px_per_unit * thickness; - vec4 world_pos = model * to_vec4(vertex); - process_clip_planes(world_pos.xyz); - gl_Position = projectionview * world_pos; - gl_Position.z += gl_Position.w * depth_shift; + gl_Position = model * to_vec4(vertex); } diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 22f2e101af3..106a07138d0 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -503,8 +503,6 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(plot::Lines)) data[:color] = pop!(data, :intensity) end - data[:num_clip_planes] = Observable(8) - return draw_lines(screen, positions, data) end end @@ -517,6 +515,11 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(plot::LineSegme Vec2f(ppu * origin(viewport)) end + # Handled manually without using OpenGL clipping + data[:_num_clip_planes] = pop!(data, :num_clip_planes) + data[:num_clip_planes] = Observable(0) + + positions = handle_view(plot[1], data) # positions = lift(apply_transform, plot, transform_func_obs(plot), positions, plot.space) positions = apply_transform_and_f32_conversion(scene, plot, positions) diff --git a/src/utilities/Plane.jl b/src/utilities/Plane.jl index be826fc0a91..5f8d8508957 100644 --- a/src/utilities/Plane.jl +++ b/src/utilities/Plane.jl @@ -100,4 +100,23 @@ function unclipped_indices(clip_planes::Vector{<: Plane3}, positions::AbstractAr else return eachindex(positions) end +end + +function perpendicular_vector(v::Vec3) + # https://math.stackexchange.com/a/4112622 + return Vec3( + copysign(v[3], v[1]), + copysign(v[3], v[2]), + -copysign(abs(v[1]) + abs(v[2]), v[3]), + ) +end + +function to_mesh(plane::Plane3{T}; origin = plane.distance * plane.normal, size = 1) where T + scale = size isa VecTypes ? size : Vec2f(size) + v1 = scale[1] * normalize(perpendicular_vector(plane.normal)) + v2 = scale[2] * normalize(cross(v1, plane.normal)) + ps = Point3f[origin - v1 - v2, origin - v1 + v2, origin + v1 - v2, origin + v1 + v2] + ns = [plane.normal for _ in 1:4] + fs = GLTriangleFace[(1,2,3), (2, 3, 4)] + return GeometryBasics.Mesh(GeometryBasics.meta(ps; normals=ns), fs) end \ No newline at end of file