Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scatter in GLMakie: overdraw and LScene axis #3412

Open
hexaeder opened this issue Nov 24, 2023 · 3 comments
Open

Scatter in GLMakie: overdraw and LScene axis #3412

hexaeder opened this issue Nov 24, 2023 · 3 comments
Labels
bug GLMakie This relates to GLMakie.jl, the OpenGL backend for Makie.

Comments

@hexaeder
Copy link
Contributor

In 3D plots in GraphMakie, we overlay the line-segments based edge plot with scatter plots containing node and edge-arrow markers like this:
grafik

In GLMakie this looks a bit weird because of the dept effect of the flat sprites:

using GLMakie
fig = Figure()
ax = LScene(fig[1,1])
lines!(ax, [(1,0,0), (0,1,1)])
scatter!(ax, [(.5,.5,.5)]; markersize=100)
grafik

scatter!(...; depth_shift=x) changes nothing and

scatter!(ax, [(.5,.5,.5)]; markersize=100, overdraw=true)

weirdly interacts with the LScene axis
grafik

Any ideas on whats happening here and how to achieve the CairoMakie-look?

@hexaeder hexaeder added the bug label Nov 24, 2023
@ffreyer
Copy link
Collaborator

ffreyer commented Nov 24, 2023

Depth shift does do something but it's not what you're looking for:
Screenshot from 2023-11-24 15-24-14

I would say there are two ways you can go about this. The first is to continue with scatter, but remove the billboarding, i.e. that scatter markers are always drawn on a plane parallel to the screen. You'll need to move your scatter plot to markerspace = :data and have Vec3 or Quaternion rotations for this. For example:

fig = Figure()
ax = LScene(fig[1,1])
lines!(ax, [(1,0,0), (0,1,1)])
# utriangle points in +y dir, width in x dir, flat in z dir
rot = 
    Makie.rotation_between(Vec3f(0, 1, 0), Vec3f(-1, 1, 1)) * # y to line dir
    Makie.rotation_between(Vec3f(1, 0, 0), Vec3f(0, 0, 1)) # x to z 

scatter!(ax, [(.5,.5,.5)]; 
    marker = :utriangle, markersize=0.5, markerspace = :data, 
    rotations = rot,
    depth_shift = -1f-3 # push scatter forward to draw over line
)
fig

Screenshot from 2023-11-24 15-44-33

The other option is to go full 3D and use meshscatter, like we do for arrows. E.g.:

fig = Figure()
ax = LScene(fig[1,1])
ps = Point3f[(1,0,0), (0,1,1)]

rot = Makie.rotation_between(Vec3f(0,0,1), ps[2] - ps[1])
len = norm(ps[2] - ps[1])
width = 0.02

# These may eventually move to GeometryBasics
cone = Makie.merge([
    # origin, radius, normal, angular resolution
    Makie._circle(Point3f(0, 0, -0.5), 0.5f0, Vec3f(0,0,-1), 16) ,
    # start pos, end pos, start radius, end radius, angular resolution
    Makie._mantle(Point3f(0, 0, -0.5), Point3f(0, 0, 0.5), 0.5f0, 0f0, 16)
])
tube = Makie.merge([
    Makie._circle(Point3f(0), 0.5f0, Vec3f(0,0,-1), 16) ,
    Makie._mantle(Point3f(0), Point3f(0, 0, 1), 0.5f0, 0.5f0, 16),
    Makie._circle(Point3f(0, 0, 1), 0.5f0, Vec3f(0,0,1), 16)
])

meshscatter!(ax, 
    [(1,0,0)], markersize = [Vec3f(width, width, len)], rotation = [rot],
    marker = tube
)

meshscatter!(ax, 
    [(.5,.5,.5)], markersize = 0.2, rotation = rot,
    marker = cone
)
fig

Screenshot from 2023-11-24 16-10-34

@hexaeder
Copy link
Contributor Author

Thanks for the examples! Unfortunately, both options bring the marker to the data space, which I'd like to avoid. This would create more problems in terms of determining a suitable marker size and so on.

Sure that dept_shift is not what I am looking for? Shifting the markers all the way to the front might solve my problems... at least in some experimentation, I got it to work with -1f0. On the other hand, I don't really understand that argument and it seems to have some global hidden state which I can't wrap my head around and which scares me:

fig = Figure()
ax = LScene(fig[1,1])
lines!(ax, [(1,0,0), (0,1,1)])
scatter!(ax, [(.5,.5,.5)]; markersize=100, depth_shift=0.01f0)
# brings to the back

fig = Figure()
ax = LScene(fig[1,1])
lines!(ax, [(1,0,0), (0,1,1)])
scatter!(ax, [(.5,.5,.5)]; markersize=100, depth_shift=-0.01)
# does not work, maybe because it's f64 not f32? But does not reset it either; still in the back

fig = Figure()
ax = LScene(fig[1,1])
lines!(ax, [(1,0,0), (0,1,1)])
scatter!(ax, [(.5,.5,.5)]; markersize=100, depth_shift=-0.01f0)
# Now it's in the front

for depth in [-0.01f0, -0.01, 0.01, 0.01f0]
    fig = Figure()
    ax = LScene(fig[1,1])
    lines!(ax, [(1,0,0), (0,1,1)])
    scatter!(ax, [(.5,.5,.5)]; markersize=100, depth_shift=depth)
    save("depth$depth.png", fig)
end
# same output 4 times, always in the front, wat? seems to depend on the last state before the loop.

@ffreyer
Copy link
Collaborator

ffreyer commented Nov 24, 2023

Ok maybe I should write my thoughts down a bit more.

Any ideas on how to achieve the CairoMakie-look?

I don't think you actually want that, because CairoMakie fully ignores depth from one plot object to the next. So if you just draw lots of markers and lines one or the other will be in front:

Scatter last Lines last
Screenshot from 2023-11-24 18-46-34 Screenshot from 2023-11-24 18-46-40

I'm assuming what you actually want is for markers to be in front of the line segment they relate to, but not globally. So if there is another line that's closer to the viewer/camera I'm assuming you'd want that to still plot over the marker. To get that behavior you'd need to find the lowest depth of the line in the area of the marker/circle and put the marker at that depth.

With this example

the depth value of the line should be constant up to float precision, because (-1, 1, 1) is perpendicular to the default camera direction (-1, -1, -1). So you just need to shift it a little to get one in front of the other. (Which is what depth_shift does. I.e. the very last transformation is basically final_z = z + depth_shift with final_z being responsible for the order of objects.)

If you rotate the camera the depth value you need changes. This is more or less the worst case:
Screenshot from 2023-11-24 20-05-24
Here the depth values are:

Makie.project(ax.scene.camera, :data, :clip, Vec3f(0, 1, 1))[3] # 0.8039375
Makie.project(ax.scene.camera, :data, :clip, Vec3f(0.5))[3]     # 0.8953843 
Makie.project(ax.scene.camera, :data, :clip, Vec3f(1, 0, 0))[3] # 0.9442493

so you'd need to shift the marker by about -0.1 to get in front of the full line. But then it's also going to be in front of everything else that's behind the end point of this line, like labels, other lines, etc. If everything is the same color that's probably fine but then I think you actually just want to get rid of the artifacts around the lines, i.e. #3408 / https://discourse.julialang.org/t/hide-stroke-in-3d-scatter-using-makie-jl/106383? Because without those you can't tell what's in front: (no shift)
Screenshot from 2023-11-24 20-46-31

And if you do want to use more than one color, then you're going to have issues with arrow markers being in front of other objects that should be in front of the marker. And you also have to do a bunch of back-and-forth transforming to figure out how much shifting you need. That's probably going to be more expensive than some heuristic for finding an acceptable markersize for meshscatter or scatter.

Shifting the markers all the way to the front might solve my problems... at least in some experimentation, I got it to work with -1f0.

That's going to get you to the CairoMakie case but you may run into issues with markers getting clipped earlier than you want to, because on -1 < depth < 1 should be drawn.

does not work, maybe because it's f64 not f32? But does not reset it either; still in the back

Yea that might be missing a convert_attribute

same output 4 times, always in the front, wat? seems to depend on the last state before the loop.

Weird, maybe there is some screen reuse going on that's messing with typing as well? backlight was doing something similar when it didn't have a convert_attribute method.

Any ideas on whats happening here? (overdraw)

Setting overdraw = true makes the plot ignore the depth buffer, both read and write. So it plots over everything that's already there but doesn't prevent later plots from plotting over it. The axis happens to have transparency = true which delays it until after all the transparency = false plots, so it draws over the overdraw.

@t-bltg t-bltg added the GLMakie This relates to GLMakie.jl, the OpenGL backend for Makie. label Dec 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug GLMakie This relates to GLMakie.jl, the OpenGL backend for Makie.
Projects
None yet
Development

No branches or pull requests

3 participants