Skip to content
This repository has been archived by the owner on Jul 14, 2021. It is now read-only.

Commit

Permalink
Merge pull request #177 from ffreyer/point_picking
Browse files Browse the repository at this point in the history
Change ranged pick to return all plots sorted by distance
  • Loading branch information
SimonDanisch authored May 7, 2021
2 parents 66d7c8a + f833e39 commit b01e8e5
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 49 deletions.
135 changes: 90 additions & 45 deletions src/screen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,38 @@ function global_gl_screen(resolution::Tuple, visibility::Bool, tries = 1)
screen
end



#################################################################################
### 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)
rx, ry = minimum(rect)
rw, rh = widths(rect)
w, h = window_size
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
end

function pick_native(screen::Screen, xy::Vec{2, Float64})
isopen(screen) || return SelectionID{Int}(0, 0)
sid = Base.RefValue{SelectionID{UInt32}}()
Expand All @@ -424,80 +456,93 @@ function pick_native(screen::Screen, xy::Vec{2, Float64})
glReadPixels(x, y, 1, 1, buff.format, buff.pixeltype, sid)
return convert(SelectionID{Int}, sid[])
end
return return SelectionID{Int}(0, 0)
return SelectionID{Int}(0, 0)
end

function pick_native(screen::Screen, xy::Vec{2, Float64}, range::Float64)
isopen(screen) || return SelectionID{Int}(0, 0)
window_size = widths(screen)
w, h = window_size
if !((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h))
return SelectionID{Int}(0, 0)
function AbstractPlotting.pick(scene::SceneLike, screen::Screen, xy::Vec{2, Float64})
sid = pick_native(screen, xy)
if haskey(screen.cache2plot, sid.id)
plot = screen.cache2plot[sid.id]
return (plot, sid.index)
else
return (nothing, 0)
end
end

fb = screen.framebuffer
buff = fb.buffers[:objectid]
glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1])
glReadBuffer(GL_COLOR_ATTACHMENT1)
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
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 = Matrix{SelectionID{UInt32}}(undef, dx, dy)
glReadPixels(x0, y0, dx, dy, buff.format, buff.pixeltype, sid)
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)
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
return id
end

function AbstractPlotting.pick(scene::SceneLike, screen::Screen, xy::Vec{2, Float64})
sid = pick_native(screen, xy)
if haskey(screen.cache2plot, sid.id)
plot = screen.cache2plot[sid.id]
return (plot, sid.index)
if haskey(screen.cache2plot, id[1])
return (screen.cache2plot[id[1]], id[2])
else
return (nothing, 0)
end
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)
# 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
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))

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
(screen.cache2plot[sid.id], sid.index)
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
return Tuple{AbstractPlot, Int}[]

idxs = sortperm(distances)
permute!(selected, idxs)
return map(id -> (screen.cache2plot[id[1]], id[2]), selected)
end


pollevents(::GLScreen) = nothing
pollevents(::Screen) = GLFW.PollEvents()
7 changes: 3 additions & 4 deletions test/unit_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,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

0 comments on commit b01e8e5

Please # to comment.