Makie: Linking cameras in multiple 3d plots

Is there a way to link cameras in multiple 3d plot. For now I use my first axis as the main one, and update the azimuth and elevation all all the other axis based on this one. However, with this solution i can change the view only with the first axis. Is there a way to do that with any axis where all the axis are linked altogether ?

For now my solution looks like that :

using GLMakie
f = Figure()

ax1 = Axis3(f[1, 1],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax1,rand(20,3))

ax2 = Axis3(f[1, 2],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax2,rand(20,3))

on(ax1.attributes.azimuth) do x
    ax2.attributes.azimuth = x
end
on(ax1.attributes.elevation) do x
    ax2.attributes.elevation = x
end

# on(ax2.attributes.azimuth) do x
#     ax1.attributes.azimuth = x
# end
# on(ax2.attributes.elevation) do x
#     ax1.attributes.elevation = x
# end

Where the commented part is what i would like but doesn’t work as it create an updating loop and makes julia crash.

You have to change the logic so the update only happens if the values differ, otherwise it’s cyclical.

Yep that’s working ! Thanks for the idea i was overthinking this problem !

For posterity this function will link all the views of Axis3 in a figure:

function link_cameras(f; step=0.01)
    axes = f.content[findall(x -> typeof(x) == Axis3,f.content)]

    for i in 1:length(axes)
        on(axes[i].attributes.azimuth) do x
            for j in collect(1:length(axes))[1:end .!= i]
                if abs(x - axes[j].attributes.azimuth[])>step
                    axes[j].attributes.azimuth = x
                end
            end
        end
        on(axes[i].attributes.elevation) do x
            for j in collect(1:length(axes))[1:end .!= i]
                if abs(x - axes[j].attributes.elevation[])>step
                    axes[j].attributes.elevation = x
                end
            end
        end
    end
    return f
end

And with a MWE:

using GLMakie
f = Figure()

ax1 = Axis3(f[1, 1],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax1,rand(20,3))

ax2 = Axis3(f[1, 2],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax2,rand(20,3))

ax3 = Axis3(f[2, 1],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax3,rand(20,3))

ax4 = Axis3(f[2, 2],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax4,rand(20,3))

f = link_cameras(f)

For posterity, here is two functions linking all views in a figure ! Functions work either with Axis3 or with Lscene.

In axis3 there is no zoom, so azimuth and elevation of all axis in a figure are linked with this function

function link_cameras_axis3(f; step=0.01)
    axes = f.content[findall(x -> typeof(x) == Axis3,f.content)]

    for i in 1:length(axes)
        on(axes[i].attributes.azimuth) do x
            for j in collect(1:length(axes))[1:end .!= i]
                if abs(x - axes[j].attributes.azimuth[])>step
                    axes[j].attributes.azimuth = x
                end
            end
        end
        on(axes[i].attributes.elevation) do x
            for j in collect(1:length(axes))[1:end .!= i]
                if abs(x - axes[j].attributes.elevation[])>step
                    axes[j].attributes.elevation = x
                end
            end
        end
    end
    return f
end

In Lscene there is a complete camera object, so when a eye position is changed, it will update all the parameters of the other LScenes of the figure with this function:

function link_cameras_lscene(f; step=0.01)
    scenes = f.content[findall(x -> typeof(x) == LScene,f.content)]
    cameras = [x.scene.camera_controls for x in scenes]

    for i in 1:length(cameras)
        on(cameras[i].eyeposition) do x
            for j in collect(1:length(cameras))[1:end .!= i]
                if sum(abs.(x - cameras[j].eyeposition[]))>step
                    cameras[j].lookat[]       = cameras[i].lookat[]
                    cameras[j].eyeposition[]  = cameras[i].eyeposition[]
                    cameras[j].upvector[]     = cameras[i].upvector[]
                    cameras[j].zoom_mult[]    = cameras[i].zoom_mult[]
        
                    update_cam!(scenes[j].scene, cameras[j])
                end
            end
        end
    end
    return f
end

Finally, here is a MWE:

using GLMakie
f = Figure()
ax1 = Axis3(f[1, 1],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax1,rand(20,3))
ax2 = Axis3(f[1, 2],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax2,rand(20,3))
ax3 = Axis3(f[2, 1],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax3,rand(20,3))
ax4 = Axis3(f[2, 2],aspect=:data, viewmode=:fitzoom)
meshscatter!(ax4,rand(20,3))
f = link_cameras_axis3(f)


f = Figure()
ax1 = LScene(f[1, 1],aspect=:data)
meshscatter!(ax1,rand(20,3))
ax2 = LScene(f[1, 2],aspect=:data)
meshscatter!(ax2,rand(20,3))
ax3 = LScene(f[2, 1],aspect=:data)
meshscatter!(ax3,rand(20,3))
ax4 = LScene(f[2, 2],aspect=:data)
meshscatter!(ax4,rand(20,3))
f = link_cameras_lscene(f)