diff --git a/WGLMakie/src/Lines.js b/WGLMakie/src/Lines.js index 8fda06ae4eb..ad9ea0de490 100644 --- a/WGLMakie/src/Lines.js +++ b/WGLMakie/src/Lines.js @@ -38,9 +38,11 @@ function linesegments_vertex_shader(uniforms, attributes) { ${attribute_decl} ${uniform_decl} + uniform int is_segments_multi; out vec2 f_uv; out ${color} f_color; + flat out uint frag_instance_id; vec2 get_resolution() { // 2 * px_per_unit doesn't make any sense, but works @@ -72,6 +74,7 @@ function linesegments_vertex_shader(uniforms, attributes) { vec2 point = pointA + xBasis * position.x + yBasis * width * position.y; gl_Position = vec4(point.xy / get_resolution(), position.x == 1.0 ? p_b.z : p_a.z, 1.0); + frag_instance_id = uint((gl_InstanceID * is_segments_multi) + int(position.x == 1.0)); } `; } @@ -86,6 +89,7 @@ function lines_fragment_shader(uniforms, attributes) { "nan_color", "highclip", "lowclip", + "picking", ]); const uniform_decl = uniforms_to_type_declaration(color_uniforms); @@ -147,10 +151,28 @@ function lines_fragment_shader(uniforms, attributes) { return aastep(threshold1, dist) * aastep(threshold2, 1.0 - dist); } + flat in uint frag_instance_id; + uniform uint object_id; + + vec4 pack_int(uint id, uint index) { + vec4 unpack; + unpack.x = float((id & uint(0xff00)) >> 8) / 255.0; + unpack.y = float((id & uint(0x00ff)) >> 0) / 255.0; + unpack.z = float((index & uint(0xff00)) >> 8) / 255.0; + unpack.w = float((index & uint(0x00ff)) >> 0) / 255.0; + return unpack; + } void main(){ + float xalpha = aastep(0.0, 0.0, f_uv.x); float yalpha = aastep(0.0, 0.0, f_uv.y); vec4 color = get_color(f_color, colormap, colorrange); + if (picking) { + if (color.a > 0.1) { + fragment_color = pack_int(object_id, frag_instance_id); + } + return; + } fragment_color = vec4(color.rgb, color.a); } `; @@ -158,13 +180,15 @@ function lines_fragment_shader(uniforms, attributes) { function create_line_material(uniforms, attributes) { const uniforms_des = deserialize_uniforms(uniforms); - return new THREE.RawShaderMaterial({ + const mat = new THREE.RawShaderMaterial({ uniforms: uniforms_des, glslVersion: THREE.GLSL3, vertexShader: linesegments_vertex_shader(uniforms_des, attributes), fragmentShader: lines_fragment_shader(uniforms_des, attributes), transparent: true, }); + mat.uniforms.object_id = { value: 1 }; + return mat; } function attach_interleaved_line_buffer(attr_name, geometry, points, ndim, is_segments) { @@ -268,6 +292,7 @@ export function _create_line(line_data, is_segments) { geometry.attributes ); + material.uniforms.is_segments_multi = {value: is_segments ? 2 : 1}; const mesh = new THREE.Mesh(geometry, material); const offset = is_segments ? 0 : 1; const new_count = geometry.attributes.linepoint_start.count; diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index 8c555209965..a623a61fd65 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -1,50 +1,8 @@ -struct ThreeDisplay - session::Bonito.Session -end - -Bonito.session(td::ThreeDisplay) = td.session -Base.empty!(::ThreeDisplay) = nothing # TODO implement - - -function Base.close(screen::ThreeDisplay) - # TODO implement -end - -function Base.size(screen::ThreeDisplay) - # look at d.qs().clientWidth for displayed width - js = js"[document.querySelector('canvas').width, document.querySelector('canvas').height]" - width, height = round.(Int, Bonito.evaljs_value(screen.session, js; timeout=100)) - return (width, height) -end - -function render_with_init(screen, session, scene) - screen.session = session - three, canvas, on_init = three_display(screen, session, scene) - screen.display = true - Makie.push_screen!(scene, screen) - on(session, on_init) do i - if !isready(screen.three) - put!(screen.three, three) - end - mark_as_displayed!(screen, scene) - return - end - return three, canvas, on_init -end - -function Bonito.jsrender(session::Session, scene::Scene) - screen = Screen(scene) - three, canvas, on_init = render_with_init(screen, session, scene) - return canvas -end - -function Bonito.jsrender(session::Session, fig::Makie.FigureLike) - Makie.update_state_before_display!(fig) - return Bonito.jsrender(session, Makie.get_scene(fig)) -end """ * `framerate = 30`: Set framerate (frames per second) to a higher number for smoother animations, or to a lower to use less resources. +* `resize_to = nothing`: Resize the canvas to the parent element with `resize_to=:parent`, or to the body if `resize_to = :body`. The default `nothing`, will resize nothing. + A tuple is allowed too, with the same values just for width/height. """ struct ScreenConfig framerate::Float64 # =30.0 @@ -54,10 +12,8 @@ struct ScreenConfig px_per_unit::Union{Nothing,Float64} # nothing, a.k.a the browser px_per_unit (devicePixelRatio) scalefactor::Union{Nothing,Float64} resize_to_body::Bool - function ScreenConfig( - framerate::Number, resize_to::Any, px_per_unit::Union{Number, Automatic, Nothing}, - scalefactor::Union{Number, Automatic, Nothing}, resize_to_body::Union{Nothing, Bool}) - + function ScreenConfig(framerate::Number, resize_to::Any, px_per_unit::Union{Number,Automatic,Nothing}, + scalefactor::Union{Number,Automatic,Nothing}, resize_to_body::Union{Nothing,Bool}) if px_per_unit isa Automatic px_per_unit = nothing end @@ -72,13 +28,74 @@ struct ScreenConfig resize_to = resize_to_body ? :body : nothing end end - ResizeType = Union{Nothing, Symbol} + ResizeType = Union{Nothing,Symbol} if !(resize_to isa Union{ResizeType,Tuple{ResizeType,ResizeType}}) error("Only nothing, :parent, or :body allowed, or a tuple of those for width/height.") end return new(framerate, resize_to, px_per_unit, scalefactor) end end +""" + Screen(args...; screen_config...) + +# Arguments one can pass via `screen_config`: + +$(Base.doc(ScreenConfig)) + +# Constructors: + +$(Base.doc(MakieScreen)) +""" +mutable struct Screen <: Makie.MakieScreen + plot_initialized::Channel{Bool} + session::Union{Nothing,Session} + scene::Union{Nothing,Scene} + displayed_scenes::Set{String} + config::ScreenConfig + canvas::Union{Nothing,Bonito.HTMLElement} + function Screen(scene::Union{Nothing,Scene}, config::ScreenConfig) + return new(Channel{Bool}(1), nothing, scene, Set{String}(), config, nothing) + end +end + +function scene_already_displayed(screen::Screen, scene=screen.scene) + scene === nothing && return false + screen.scene === scene || return false + screen.canvas === nothing && return false + return !isnothing(screen.session) && + isready(screen.session) && isready(screen.plot_initialized) && + js_uuid(screen.scene) in screen.displayed_scenes +end + +function render_with_init(screen::Screen, session::Session, scene::Scene) + # Reference to three object which gets set once we serve this to a browser + # Make sure it's a new Channel, since we may re-use the screen. + screen.plot_initialized = Channel{Bool}(1) + screen.session = session + Makie.push_screen!(scene, screen) + canvas, on_init = three_display(screen, session, scene) + screen.canvas = canvas + on(session, on_init) do initialized + if !isready(screen.plot_initialized) && initialized + put!(screen.plot_initialized, true) + mark_as_displayed!(screen, scene) + else + error("Three object should be ready after init, but isn't - connection interrupted? Session: $(session), initialized: $(initialized)") + end + return + end + return canvas +end + +function Bonito.jsrender(session::Session, scene::Scene) + screen = Screen(scene) + return render_with_init(screen, session, scene) +end + +function Bonito.jsrender(session::Session, fig::Makie.FigureLike) + Makie.update_state_before_display!(fig) + return Bonito.jsrender(session, Makie.get_scene(fig)) +end """ @@ -112,56 +129,33 @@ function Bonito.jsrender(session::Session, wconfig::WithConfig) Makie.update_state_before_display!(fig) scene = Makie.get_scene(fig) screen = Screen(scene, wconfig.config) - three, canvas, on_init = render_with_init(screen, session, scene) - return canvas + return render_with_init(screen, session, scene) end -""" - Screen(args...; screen_config...) - -# Arguments one can pass via `screen_config`: - -$(Base.doc(ScreenConfig)) - -# Constructors: - -$(Base.doc(MakieScreen)) -""" -mutable struct Screen <: Makie.MakieScreen - three::Channel{ThreeDisplay} - session::Union{Nothing, Session} - display::Any - scene::Union{Nothing, Scene} - displayed_scenes::Set{String} - config::ScreenConfig - function Screen( - three::Channel{ThreeDisplay}, - display::Any, - scene::Union{Nothing, Scene}, config::ScreenConfig) - return new(three, nothing, display, scene, Set{String}(), config) - end -end - function Base.show(io::IO, screen::Screen) c = screen.config ppu = c.px_per_unit sf = c.scalefactor - print(io, """WGLMakie.Screen( - framerate = $(c.framerate), - resize_to = $(c.resize_to), - px_per_unit = $(isnothing(ppu) ? :automatic : ppu), - scalefactor = $(isnothing(sf) ? :automatic : sf) - )""") + three_str = sprint(io -> show(io, MIME"text/plain"(), screen.plot_initialized)) + return print(io, """WGLMakie.Screen( + framerate = $(c.framerate), + resize_to = $(c.resize_to), + px_per_unit = $(isnothing(ppu) ? :automatic : ppu), + scalefactor = $(isnothing(sf) ? :automatic : sf), + session = $(isnothing(screen.session) ? :nothing : isopen(screen.session)), + three = $(three_str), + scene = $(isnothing(screen.scene) ? :nothing : screen.scene), + )""") end # Resizing the scene is enough for WGLMakie Base.resize!(::WGLMakie.Screen, w, h) = nothing function Base.isopen(screen::Screen) - three = get_three(screen) - return !isnothing(three) && isopen(three.session) + session = get_screen_session(screen) + return !isnothing(session) && isopen(session) end function mark_as_displayed!(screen::Screen, scene::Scene) @@ -176,11 +170,9 @@ for M in Makie.WEB_MIMES @eval begin function Makie.backend_show(screen::Screen, io::IO, m::$M, scene::Scene) inline_display = App() do session::Session - three, canvas, init_obs = render_with_init(screen, session, scene) - return canvas + return render_with_init(screen, session, scene) end Base.show(io, m, inline_display) - screen.display = true return screen end end @@ -197,46 +189,36 @@ function Base.size(screen::Screen) return size(screen.scene) end -function get_three(screen::Screen; timeout = 100, error::Union{Nothing, String}=nothing)::Union{Nothing, ThreeDisplay} +function get_screen_session(screen::Screen; timeout=100, + error::Union{Nothing,String}=nothing)::Union{Nothing,Session} function throw_error(status) if !isnothing(error) message = "Can't get three: $(status)\n$(error)" Base.error(message) end end - if screen.display !== true - throw_error("Screen hasn't displayed yet, so can't get connection to three") - return nothing - end if isnothing(screen.session) - throw_error("Screen has no session. Not yet displayed?"); return nothing + throw_error("Screen has no session. Not yet displayed?") + return nothing end if !(screen.session.status in (Bonito.RENDERED, Bonito.DISPLAYED, Bonito.OPEN)) - throw_error("Screen Session uninitialized. Not yet displayed? Session status: $(screen.session.status)"); return nothing + throw_error("Screen Session uninitialized. Not yet displayed? Session status: $(screen.session.status)") + return nothing end - tstart = time() - result = nothing - while true - yield() - if time() - tstart > timeout - break # we waited LONG ENOUGH!! - end - if isready(screen.three) - result = fetch(screen.three) - break - end + success = Bonito.wait_for_ready(screen.session; timeout=timeout) + if success !== :success + throw_error("Timed out waiting for session to get ready") + return nothing end + success = Bonito.wait_for(() -> isready(screen.plot_initialized); timeout=timeout) # Throw error if error message specified - if isnothing(result) + if success !== :success throw_error("Timed out waiting $(timeout)s for session to get initilize") end - return result + # At this point we should have a fully initialized plot + session + return screen.session end -function Makie.apply_screen_config!(screen::ThreeDisplay, config::ScreenConfig, args...) - #TODO implement - return screen -end function Makie.apply_screen_config!(screen::Screen, config::ScreenConfig, args...) #TODO implement return screen @@ -244,38 +226,33 @@ end # TODO, create optimized screens, forward more options to JS/WebGL function Screen(scene::Scene; kw...) - config = Makie.merge_screen_config(ScreenConfig, Dict{Symbol, Any}(kw)) - return Screen(Channel{ThreeDisplay}(1), nothing, scene, config) + config = Makie.merge_screen_config(ScreenConfig, Dict{Symbol,Any}(kw)) + return Screen(scene, config) end -Screen(scene::Scene, config::ScreenConfig) = Screen(Channel{ThreeDisplay}(1), nothing, scene, config) Screen(scene::Scene, config::ScreenConfig, ::IO, ::MIME) = Screen(scene, config) Screen(scene::Scene, config::ScreenConfig, ::Makie.ImageStorageFormat) = Screen(scene, config) function Base.empty!(screen::Screen) screen.scene = nothing - screen.display = false + screen.plot_initialized = Channel{Bool}(1) + return # TODO, empty state in JS, to be able to reuse screen end -Makie.wait_for_display(screen::Screen) = get_three(screen) +Makie.wait_for_display(screen::Screen) = get_screen_session(screen) function Base.display(screen::Screen, scene::Scene; unused...) - Makie.push_screen!(scene, screen) - # Reference to three object which gets set once we serve this to a browser + # already displayed! + if scene_already_displayed(screen, scene) + return screen + end app = App() do session - screen.session = session - three, canvas, done_init = three_display(screen, session, scene) - on(session, done_init) do _ - put!(screen.three, three) - mark_as_displayed!(screen, scene) - return - end - return canvas + return render_with_init(screen, session, scene) end display(app) - screen.display = true + Bonito.wait_for_ready(screen.session) # wait for plot to be full initialized, so that operations don't get racy (e.g. record/RamStepper & friends) - get_three(screen) + get_screen_session(screen; error="Waiting for plot to be initialized in display") return screen end @@ -296,27 +273,27 @@ function session2image(session::Session, scene::Scene) end function Makie.colorbuffer(screen::Screen) - if screen.display !== true + if isnothing(screen.session) Base.display(screen, screen.scene) end - three = get_three(screen; error="Not able to show scene in a browser") - return session2image(three.session, screen.scene) + session = get_screen_session(screen; error="Not able to show scene in a browser") + return session2image(session, screen.scene) end -function insert_scene!(disp, screen::Screen, scene::Scene) +function insert_scene!(session::Session, screen::Screen, scene::Scene) if js_uuid(scene) in screen.displayed_scenes return true else if !(js_uuid(scene.parent) in screen.displayed_scenes) # Parents serialize their child scenes, so we only need to # serialize & update the parent scene - return insert_scene!(disp, screen, scene.parent) + return insert_scene!(session, screen, scene.parent) end scene_ser = serialize_scene(scene) parent = scene.parent parent_uuid = js_uuid(parent) err = "Cant find scene js_uuid(scene) == $(parent_uuid)" - evaljs_value(disp.session, js""" + evaljs_value(session, js""" $(WGL).then(WGL=> { const parent = WGL.find_scene($(parent_uuid)); if (!parent) { @@ -331,9 +308,9 @@ function insert_scene!(disp, screen::Screen, scene::Scene) end end -function insert_plot!(disp::ThreeDisplay, scene::Scene, @nospecialize(plot::Plot)) +function insert_plot!(session::Session, scene::Scene, @nospecialize(plot::Plot)) plot_data = serialize_plots(scene, [plot]) - plot_sub = Session(disp.session) + plot_sub = Session(session) Bonito.init_session(plot_sub) plot.__wgl_session = plot_sub js = js""" @@ -345,9 +322,9 @@ function insert_plot!(disp::ThreeDisplay, scene::Scene, @nospecialize(plot::Plot end function Base.insert!(screen::Screen, scene::Scene, @nospecialize(plot::Plot)) - disp = get_three(screen; error="Plot needs to be displayed to insert additional plots") + session = get_screen_session(screen; error="Plot needs to be displayed to insert additional plots") if js_uuid(scene) in screen.displayed_scenes - insert_plot!(disp, scene, plot) + insert_plot!(session, scene, plot) else # Newly created scene gets inserted! # This must be a child plot of some parent, otherwise a plot wouldn't be inserted via `insert!(screen, ...)` @@ -360,17 +337,17 @@ function Base.insert!(screen::Screen, scene::Scene, @nospecialize(plot::Plot)) # We serialize the whole scene (containing `plot` as well), # since, we should only get here if scene is newly created and this is the first plot we insert! @assert scene.plots[1] == plot - insert_scene!(disp, screen, scene) + insert_scene!(session, screen, scene) end return end function delete_js_objects!(screen::Screen, plot_uuids::Vector{String}, session::Union{Nothing,Session}) - three = get_three(screen) - isnothing(three) && return # if no session we haven't displayed and dont need to delete - isready(three.session) || return - Bonito.evaljs(three.session, js""" + main_session = get_screen_session(screen) + isnothing(main_session) && return # if no session we haven't displayed and dont need to delete + isready(main_session) || return + Bonito.evaljs(main_session, js""" $(WGL).then(WGL=> { WGL.delete_plots($(plot_uuids)); })""") @@ -388,24 +365,23 @@ function all_plots_scenes(scene::Scene; scene_uuids=String[], plots=Plot[]) end function delete_js_objects!(screen::Screen, scene::Scene) - three = get_three(screen) - isnothing(three) && return # if no session we haven't displayed and dont need to delete - isready(three.session) || return + session = get_screen_session(screen) + isnothing(session) && return # if no session we haven't displayed and dont need to delete + isready(session) || return scene_uuids, plots = all_plots_scenes(scene) for plot in plots if haskey(plot, :__wgl_session) - session = plot.__wgl_session[] - close(session) + wgl_session = plot.__wgl_session[] + close(wgl_session) end end - Bonito.evaljs(three.session, js""" + Bonito.evaljs(session, js""" $(WGL).then(WGL=> { WGL.delete_scenes($scene_uuids, $(js_uuid.(plots))); })""") return end - struct LockfreeQueue{T,F} # Double buffering to be lock free queue1::Vector{T} @@ -463,7 +439,7 @@ function Base.push!(queue::LockfreeQueue, item) end const DISABLE_JS_FINALZING = Base.RefValue(false) -const DELETE_QUEUE = LockfreeQueue{Tuple{Screen, Vector{String}, Union{Session, Nothing}}}(delete_js_objects!) +const DELETE_QUEUE = LockfreeQueue{Tuple{Screen,Vector{String},Union{Session,Nothing}}}(delete_js_objects!) const SCENE_DELETE_QUEUE = LockfreeQueue{Tuple{Screen,Scene}}(delete_js_objects!) function Base.delete!(screen::Screen, scene::Scene, plot::Plot) diff --git a/WGLMakie/src/lines.jl b/WGLMakie/src/lines.jl index 9066d676374..a69e160135b 100644 --- a/WGLMakie/src/lines.jl +++ b/WGLMakie/src/lines.jl @@ -2,7 +2,6 @@ function serialize_three(scene::Scene, plot::Union{Lines, LineSegments}) Makie.@converted_attribute plot (linewidth,) uniforms = Dict( :model => plot.model, - :object_id => 1, :depth_shift => plot.depth_shift, :picking => false, ) diff --git a/WGLMakie/src/picking.jl b/WGLMakie/src/picking.jl index 15ed8f4606c..84f5876ce15 100644 --- a/WGLMakie/src/picking.jl +++ b/WGLMakie/src/picking.jl @@ -2,7 +2,7 @@ function pick_native(screen::Screen, rect::Rect2i) (x, y) = minimum(rect) (w, h) = widths(rect) - session = get_three(screen; error="Can't do picking!").session + session = get_screen_session(screen; error="Can't do picking!") scene = screen.scene picking_data = Bonito.evaljs_value(session, js""" Promise.all([$(WGL), $(scene)]).then(([WGL, scene]) => WGL.pick_native_matrix(scene, $x, $y, $w, $h)) @@ -36,7 +36,7 @@ function Makie.pick_closest(scene::Scene, screen::Screen, xy, range::Integer) # isopen(screen) || return (nothing, 0) xy_vec = Cint[round.(Cint, xy)...] range = round(Int, range) - session = get_three(screen; error="Can't do picking!").session + session = get_screen_session(screen; error="Can't do picking!") selection = Bonito.evaljs_value(session, js""" Promise.all([$(WGL), $(scene)]).then(([WGL, scene]) => WGL.pick_closest(scene, $(xy_vec), $(range))) """) @@ -49,7 +49,7 @@ function Makie.pick_sorted(scene::Scene, screen::Screen, xy, range) xy_vec = Cint[round.(Cint, xy)...] range = round(Int, range) - session = get_three(screen; error="Can't do picking!").session + session = get_screen_session(screen; error="Can't do picking!") selection = Bonito.evaljs_value(session, js""" Promise.all([$(WGL), $(scene)]).then(([WGL, scene]) => WGL.pick_sorted(scene, $(xy_vec), $(range))) """) diff --git a/WGLMakie/src/three_plot.jl b/WGLMakie/src/three_plot.jl index 581a72ed1a4..aee3f71f7b8 100644 --- a/WGLMakie/src/three_plot.jl +++ b/WGLMakie/src/three_plot.jl @@ -63,7 +63,5 @@ function three_display(screen::Screen, session::Session, scene::Scene) window_open[] = true end connect_scene_events!(scene, comm) - three = ThreeDisplay(session) - return three, wrapper, done_init + return wrapper, done_init end -| diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 924b0533e0d..5bbf870dd6c 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -21325,9 +21325,11 @@ function linesegments_vertex_shader(uniforms, attributes) { ${attribute_decl} ${uniform_decl} + uniform int is_segments_multi; out vec2 f_uv; out ${color} f_color; + flat out uint frag_instance_id; vec2 get_resolution() { // 2 * px_per_unit doesn't make any sense, but works @@ -21359,6 +21361,7 @@ function linesegments_vertex_shader(uniforms, attributes) { vec2 point = pointA + xBasis * position.x + yBasis * width * position.y; gl_Position = vec4(point.xy / get_resolution(), position.x == 1.0 ? p_b.z : p_a.z, 1.0); + frag_instance_id = uint((gl_InstanceID * is_segments_multi) + int(position.x == 1.0)); } `; } @@ -21369,7 +21372,8 @@ function lines_fragment_shader(uniforms, attributes) { "colormap", "nan_color", "highclip", - "lowclip" + "lowclip", + "picking" ]); const uniform_decl = uniforms_to_type_declaration(color_uniforms); return `#extension GL_OES_standard_derivatives : enable @@ -21430,23 +21434,45 @@ function lines_fragment_shader(uniforms, attributes) { return aastep(threshold1, dist) * aastep(threshold2, 1.0 - dist); } + flat in uint frag_instance_id; + uniform uint object_id; + + vec4 pack_int(uint id, uint index) { + vec4 unpack; + unpack.x = float((id & uint(0xff00)) >> 8) / 255.0; + unpack.y = float((id & uint(0x00ff)) >> 0) / 255.0; + unpack.z = float((index & uint(0xff00)) >> 8) / 255.0; + unpack.w = float((index & uint(0x00ff)) >> 0) / 255.0; + return unpack; + } void main(){ + float xalpha = aastep(0.0, 0.0, f_uv.x); float yalpha = aastep(0.0, 0.0, f_uv.y); vec4 color = get_color(f_color, colormap, colorrange); + if (picking) { + if (color.a > 0.1) { + fragment_color = pack_int(object_id, frag_instance_id); + } + return; + } fragment_color = vec4(color.rgb, color.a); } `; } function create_line_material(uniforms, attributes) { const uniforms_des = deserialize_uniforms(uniforms); - return new THREE.RawShaderMaterial({ + const mat = new THREE.RawShaderMaterial({ uniforms: uniforms_des, glslVersion: THREE.GLSL3, vertexShader: linesegments_vertex_shader(uniforms_des, attributes), fragmentShader: lines_fragment_shader(uniforms_des, attributes), transparent: true }); + mat.uniforms.object_id = { + value: 1 + }; + return mat; } function attach_interleaved_line_buffer(attr_name, geometry, points, ndim, is_segments) { const skip_elems = is_segments ? 2 * ndim : ndim; @@ -21522,6 +21548,9 @@ function _create_line(line_data, is_segments) { const buffers = {}; create_line_buffers(geometry, buffers, line_data.attributes, is_segments); const material = create_line_material(line_data.uniforms, geometry.attributes); + material.uniforms.is_segments_multi = { + value: is_segments ? 2 : 1 + }; const mesh = new THREE.Mesh(geometry, material); const offset = is_segments ? 0 : 1; const new_count = geometry.attributes.linepoint_start.count; diff --git a/src/interaction/ray_casting.jl b/src/interaction/ray_casting.jl index 8b3c67f2515..75f52c77e18 100644 --- a/src/interaction/ray_casting.jl +++ b/src/interaction/ray_casting.jl @@ -251,7 +251,10 @@ function position_on_plot(plot::Union{Scatter, MeshScatter}, idx, ray::Ray; appl end function position_on_plot(plot::Union{Lines, LineSegments}, idx, ray::Ray; apply_transform = true) - p0, p1 = apply_transform_and_model(plot, plot[1][][idx-1:idx]) + if idx == 1 + idx = 2 + end + p0, p1 = apply_transform_and_model(plot, plot[1][][(idx-1):idx]) pos = closest_point_on_line(p0, p1, ray)