From 3ccb8bfdd02153e4044e467586913a82b2ed3d6c Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 10 Apr 2021 19:59:49 +0200 Subject: [PATCH 1/4] adjust ranged pick --- src/screen.jl | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/screen.jl b/src/screen.jl index 266db2c..27a87c0 100644 --- a/src/screen.jl +++ b/src/screen.jl @@ -434,17 +434,26 @@ function pick_native(screen::Screen, xy::Vec{2, Float64}, range::Float64) sid = Matrix{SelectionID{UInt32}}(undef, dx, dy) glReadPixels(x0, y0, dx, dy, buff.format, buff.pixeltype, sid) - min_dist = range^2 - id = SelectionID{Int}(0, 0) + # get unique (plt, idx) pairs with the lowest distance from the cursor + ids = SelectionID{Int}[] + distances = Float64[] x, y = xy .+ 1 .- Vec2f0(x0, y0) for i in 1:dx, j in 1:dy - d = (x-i)^2 + (y-j)^2 - if (d < min_dist) && (sid[i, j][1] > 0x00000000) && (sid[i, j][2] < 0x3f800000) - min_dist = d + if (sid[i, j][1] > 0x00000000) && (sid[i, j][2] < 0x3f800000) id = convert(SelectionID{Int}, sid[i, j]) + d = (x-i)^2 + (y-j)^2 + i = findfirst(isequal(id), ids) + if i === nothing + push!(ids, id) + push!(distances, d) + elseif distances[i] > d + distances[i] = d + end end end - return id + + idxs = sortperm(distances) + return ids[idxs] end function AbstractPlotting.pick(scene::SceneLike, screen::Screen, xy::Vec{2, Float64}) @@ -458,12 +467,9 @@ function AbstractPlotting.pick(scene::SceneLike, screen::Screen, xy::Vec{2, Floa end function AbstractPlotting.pick(scene::SceneLike, screen::Screen, xy::Vec{2, Float64}, range::Float64) - sid = pick_native(screen, xy, range) - if haskey(screen.cache2plot, sid.id) - plot = screen.cache2plot[sid.id] - return (plot, sid.index) - else - return (nothing, 0) + sids = pick_native(screen, xy, range) + return map(sids) do sid + (screen.cache2plot[sid.id], sid.index) end end From bbcfd085f0098b1bf8deac43b8b0f061a34cde54 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Apr 2021 18:56:58 +0200 Subject: [PATCH 2/4] add pick_native(screen, rect) -> matrix, cleanup --- src/screen.jl | 99 +++++++++++++++++++-------------------------------- 1 file changed, 37 insertions(+), 62 deletions(-) diff --git a/src/screen.jl b/src/screen.jl index 27a87c0..372976c 100644 --- a/src/screen.jl +++ b/src/screen.jl @@ -398,62 +398,53 @@ function global_gl_screen(resolution::Tuple, visibility::Bool, tries = 1) screen end -function pick_native(screen::Screen, xy::Vec{2, Float64}) - isopen(screen) || return SelectionID{Int}(0, 0) - sid = Base.RefValue{SelectionID{UInt32}}() + + +################################################################################# +### Point picking +################################################################################ + + + +function pick_native(screen::Screen, rect::IRect2D) + isopen(screen) || return Matrix{SelectionID{Int}}(undef, 0, 0) window_size = widths(screen) fb = screen.framebuffer buff = fb.buffers[:objectid] glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) glReadBuffer(GL_COLOR_ATTACHMENT1) - x, y = floor.(Int, xy) + rx, ry = minimum(rect) + rw, rh = widths(rect) w, h = window_size - if x > 0 && y > 0 && x <= w && y <= h - glReadPixels(x, y, 1, 1, buff.format, buff.pixeltype, sid) - return convert(SelectionID{Int}, sid[]) + sid = zeros(SelectionID{UInt32}, widths(rect)...) + if rx > 0 && ry > 0 && rx + rw <= w && ry + rh <= h + glReadPixels(rx, ry, rw, rh, buff.format, buff.pixeltype, sid) + for i in eachindex(sid) + if sid[i][2] > 0x3f800000 + sid[i] = SelectionID(0, sid[i].index) + end + end + return sid + else + error("Pick region $rect out of screen bounds ($w, $h).") end - return return SelectionID{Int}(0, 0) end -function pick_native(screen::Screen, xy::Vec{2, Float64}, range::Float64) +function pick_native(screen::Screen, xy::Vec{2, Float64}) isopen(screen) || return SelectionID{Int}(0, 0) + sid = Base.RefValue{SelectionID{UInt32}}() window_size = widths(screen) - w, h = window_size - if !((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h)) - return SelectionID{Int}(0, 0) - end - fb = screen.framebuffer buff = fb.buffers[:objectid] glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) glReadBuffer(GL_COLOR_ATTACHMENT1) - - x0, y0 = max.(1, floor.(Int, xy .- range)) - x1, y1 = min.([w, h], floor.(Int, xy .+ range)) - dx = x1 - x0; dy = y1 - y0 - sid = Matrix{SelectionID{UInt32}}(undef, dx, dy) - glReadPixels(x0, y0, dx, dy, buff.format, buff.pixeltype, sid) - - # get unique (plt, idx) pairs with the lowest distance from the cursor - ids = SelectionID{Int}[] - distances = Float64[] - x, y = xy .+ 1 .- Vec2f0(x0, y0) - for i in 1:dx, j in 1:dy - if (sid[i, j][1] > 0x00000000) && (sid[i, j][2] < 0x3f800000) - id = convert(SelectionID{Int}, sid[i, j]) - d = (x-i)^2 + (y-j)^2 - i = findfirst(isequal(id), ids) - if i === nothing - push!(ids, id) - push!(distances, d) - elseif distances[i] > d - distances[i] = d - end - end + x, y = floor.(Int, xy) + w, h = window_size + if x > 0 && y > 0 && x <= w && y <= h + glReadPixels(x, y, 1, 1, buff.format, buff.pixeltype, sid) + return convert(SelectionID{Int}, sid[]) end - - idxs = sortperm(distances) - return ids[idxs] + return SelectionID{Int}(0, 0) end function AbstractPlotting.pick(scene::SceneLike, screen::Screen, xy::Vec{2, Float64}) @@ -466,32 +457,16 @@ function AbstractPlotting.pick(scene::SceneLike, screen::Screen, xy::Vec{2, Floa end end -function AbstractPlotting.pick(scene::SceneLike, screen::Screen, xy::Vec{2, Float64}, range::Float64) - sids = pick_native(screen, xy, range) - return map(sids) do sid - (screen.cache2plot[sid.id], sid.index) - end -end - -function AbstractPlotting.pick(screen::Screen, rect::IRect2D) - window_size = widths(screen) - fb = screen.framebuffer - buff = fb.buffers[:objectid] - glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) - glReadBuffer(GL_COLOR_ATTACHMENT1) - x, y = minimum(rect) - rw, rh = widths(rect) - w, h = window_size - sid = zeros(SelectionID{UInt32}, widths(rect)...) - if x > 0 && y > 0 && x <= w && y <= h - glReadPixels(x, y, rw, rh, buff.format, buff.pixeltype, sid) - sid = filter(x -> x.id < 0x3f800000,sid) - return map(unique(vec(SelectionID{Int}.(sid)))) do sid +function AbstractPlotting.pick(scene::SceneLike, screen::Screen, rect::IRect2D) + map(pick_native(screen, rect)) do sid + if haskey(screen.cache2plot, sid.id) (screen.cache2plot[sid.id], sid.index) + else + (nothing, sid.index) end end - return Tuple{AbstractPlot, Int}[] end + pollevents(::GLScreen) = nothing pollevents(::Screen) = GLFW.PollEvents() From 02ac01fd3e8e1a5551495bd5f9b5016f3face544 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Apr 2021 19:42:30 +0200 Subject: [PATCH 3/4] add more efficient overloads --- src/screen.jl | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/screen.jl b/src/screen.jl index 372976c..248f484 100644 --- a/src/screen.jl +++ b/src/screen.jl @@ -468,5 +468,69 @@ function AbstractPlotting.pick(scene::SceneLike, screen::Screen, rect::IRect2D) end +# Skips one set of allocations +function AbstractPlotting.pick_closest(scene::SceneLike, screen::Screen, xy, range) + isopen(screen) || return (nothing, 0) + w, h = widths(screen) + ((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h)) || return (nothing, 0) + + x0, y0 = max.(1, floor.(Int, xy .- range)) + x1, y1 = min.([w, h], floor.(Int, xy .+ range)) + dx = x1 - x0; dy = y1 - y0 + sid = pick_native(screen, IRect2D(x0, y0, dx, dy)) + + min_dist = range^2 + id = SelectionID{Int}(0, 0) + x, y = xy .+ 1 .- Vec2f0(x0, y0) + for i in 1:dx, j in 1:dy + d = (x-i)^2 + (y-j)^2 + if (d < min_dist) && (sid[i, j][1] > 0x00000000) && + (sid[i, j][2] < 0x3f800000) && haskey(screen.cache2plot, sid[i, j][1]) + min_dist = d + id = convert(SelectionID{Int}, sid[i, j]) + end + end + + if haskey(screen.cache2plot, id[1]) + return (screen.cache2plot[id[1]], id[2]) + else + return (nothing, 0) + end +end + +# Skips some allocations +function AbstractPlotting.pick_sorted(scene::SceneLike, screen::Screen, xy, range) + isopen(screen) || return (nothing, 0) + w, h = widths(screen) + if !((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h)) + return Tuple{AbstractPlot, Int}[] + end + x0, y0 = max.(1, floor.(Int, xy .- range)) + x1, y1 = min.([w, h], floor.(Int, xy .+ range)) + dx = x1 - x0; dy = y1 - y0 + + picks = pick_native(screen, IRect2D(x0, y0, dx, dy)) + + selected = filter(x -> x[1] > 0 && haskey(screen.cache2plot, x[1]), unique(vec(picks))) + distances = [range^2 for _ in selected] + x, y = xy .+ 1 .- Vec2f0(x0, y0) + for i in 1:dx, j in 1:dy + if picks[i, j][1] > 0 + d = (x-i)^2 + (y-j)^2 + i = findfirst(isequal(picks[i, j]), selected) + if i === nothing + @warn "This shouldn't happen..." + elseif distances[i] > d + distances[i] = d + end + end + end + + idxs = sortperm(distances) + permute!(selected, idxs) + return map(id -> (screen.cache2plot[id[1]], id[2]), selected) +end + + pollevents(::GLScreen) = nothing pollevents(::Screen) = GLFW.PollEvents() From f833e39543705785da2bd14708d667a3b87dbe24 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 17 Apr 2021 19:42:39 +0200 Subject: [PATCH 4/4] fix tests --- test/unit_tests.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/unit_tests.jl b/test/unit_tests.jl index d59be82..858dd9f 100644 --- a/test/unit_tests.jl +++ b/test/unit_tests.jl @@ -40,13 +40,12 @@ end origin_px = project_sp(ax.scene, Point(origin(rect))) tip_px = project_sp(ax.scene, Point(origin(rect) .+ widths(rect))) rect_px = IRect2D(round.(origin_px), round.(tip_px .- origin_px)) - #! there is no pick(::Scene,::IRect2D) - plot_idx = pick(screen, rect_px) + picks = unique(pick(ax.scene, rect_px)) # objects returned in plot_idx should be either grid lines (i.e. LineSegments) or Scatter points - @test all(pi-> pi[1] isa Union{LineSegments,Scatter, AbstractPlotting.Mesh}, plot_idx) + @test all(pi-> pi[1] isa Union{LineSegments,Scatter, AbstractPlotting.Mesh}, picks) # scatter points should have indices equal to those in 99991:99998 - scatter_plot_idx = filter(pi -> pi[1] isa Scatter, plot_idx) + scatter_plot_idx = filter(pi -> pi[1] isa Scatter, picks) @test Set(last.(scatter_plot_idx)) == Set(99991:99998) end end