-
-
Notifications
You must be signed in to change notification settings - Fork 290
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
Reference frame like in CAD software ? #2624
Comments
I think the way to go about this is to create a separate Scene for the indicator and update its view matrix based on the parent plot. With an # Your plot
fig = Figure()
lscene = LScene(fig[1,1])
p = scatter!(rand(Point3f, 10))
display(fig)
scene = Scene(
fig.scene, px_area = Rect2f(20, 20, 100, 100),
# backgroundcolor = RGBAf(1,1,0), clear = true # to see scene region
);
linesegments!(scene,
Point3f[(0,0,0), (1,0,0), (0,0,0), (0,1,0), (0,0,0), (0,0,1)],
color = [:red, :red, :green, :green, :blue, :blue],
linewidth = 10
)
cam = cameracontrols(lscene.scene)
scene.camera.projection[] = Makie.perspectiveprojection(45f0, 1f0, 0.01f0, 100f0)
onany(cam.lookat, cam.eyeposition, cam.upvector) do lookat, eyepos, up
viewdir = 4f0 * normalize(eyepos - lookat)
scene.camera.view[] = Makie.lookat(viewdir, Vec3f(0), up)
return
end With an Axis3 you need to read out the relevant angles and calculate eyeposition from that: fig = Figure()
ax = Axis3(fig[1,1])
p = scatter!(ax, rand(Point3f, 10))
display(fig)
scene = Scene(
fig.scene, px_area = Rect2f(20, 20, 100, 100),
backgroundcolor = RGBAf(1,1,0), clear = true # to see scene region
);
linesegments!(scene,
Point3f[(0,0,0), (1,0,0), (0,0,0), (0,1,0), (0,0,0), (0,0,1)],
color = [:red, :red, :green, :green, :blue, :blue],
linewidth = 10
)
scene.camera.projection[] = Makie.perspectiveprojection(45f0, 1f0, 0.01f0, 100f0)
onany(ax.elevation, ax.azimuth) do theta, phi
viewdir = 4f0 * Vec3f(cos(theta) * cos(phi), cos(theta) * sin(phi), sin(theta))
scene.camera.view[] = Makie.lookat(viewdir, Vec3f(0), Vec3f(0,0,1))
return
end |
Thanks @ffreyer ! I will test that ASAP. |
It's possible but more cumbersome. Within an Why not just write a function? |
I guess I just thought using a recipe would make it more versatile. Also, I'd like to remain as close as possible to |
I think you can specify a Vector of Vec3f as a rotation (per string/position). If not a Vector of (Makie) Quaternionf's should work ( |
I put something interactive together. You can click on the mesh to switch to different views. using GLMakie
using GeometryBasics, Statistics, LinearAlgebra
function gen_mesh()
o = 1 / (1 + sqrt(2))
ps = Point3f[
(-o, -o, -1), (-o, o, -1), (o, o, -1), (o, -o, -1),
(-1, o, -o), (-o, 1, -o), (o, 1, -o), (1, o, -o),
(1, -o, -o), (o, -1, -o), (-o, -1, -o), (-1, -o, -o),
(-1, o, o), (-o, 1, o), (o, 1, o), (1, o, o),
(1, -o, o), (o, -1, o), (-o, -1, o), (-1, -o, o),
(-o, -o, 1), (-o, o, 1), (o, o, 1), (o, -o, 1),
]
QF = QuadFace
TF = TriangleFace
faces = [
# bottom quad
QF(1, 2, 3, 4),
# bottom triangles
TF(2, 5, 6), TF(3, 7, 8), TF(4, 9, 10), TF(1, 11, 12),
# bottom diag quads
QF(3, 2, 6, 7), QF(4, 3, 8, 9), QF(1, 4, 10, 11), QF(2, 1, 12, 5),
# quad ring
QF(13, 14, 6, 5), QF(14, 15, 7, 6), QF(15, 16, 8, 7), QF(16, 17, 9, 8),
QF(17, 18, 10, 9), QF(18, 19, 11, 10), QF(19, 20, 12, 11), QF(20, 13, 5, 12),
# top diag quads
QF(22, 23, 15, 14), QF(21, 22, 13, 20), QF(24, 21, 19, 18), QF(23, 24, 17, 16),
# top triangles
TF(21, 20, 19), TF(24, 18, 17), TF(23, 16, 15), TF(22, 14, 13),
# top
QF(21, 24, 23, 22)
]
remapped_ps = Point3f[]
remapped_fs = AbstractFace[]
remapped_cs = RGBf[]
remapped_index = Int[]
for (idx, f) in enumerate(faces)
i = length(remapped_ps)
append!(remapped_ps, ps[f])
push!(remapped_fs, length(f) == 3 ? TF(i+1, i+2, i+3) : QF(i+1, i+2, i+3, i+4))
c = RGBf(abs.(mean(ps[f]))...)
append!(remapped_cs, (c for _ in f))
append!(remapped_index, [idx for _ in f])
end
_faces = decompose(GLTriangleFace, remapped_fs)
return GeometryBasics.Mesh(
meta(
remapped_ps;
normals = normals(remapped_ps, _faces),
color = remapped_cs,
index = remapped_index
),
_faces
)
end
function connect_camera!(ax::Axis3, scene::Scene)
scene.camera.projection[] = Makie.perspectiveprojection(45f0, 1f0, 0.01f0, 100f0)
onany(ax.elevation, ax.azimuth) do theta, phi
viewdir = 3f0 * Vec3f(cos(theta) * cos(phi), cos(theta) * sin(phi), sin(theta))
scene.lights[1].position[] = viewdir
scene.camera.view[] = Makie.lookat(viewdir, Vec3f(0), Vec3f(0,0,1))
return
end
notify(ax.elevation)
return
end
function connect_camera!(lscene::LScene, scene::Scene)
cam = cameracontrols(lscene.scene)
scene.camera.projection[] = Makie.perspectiveprojection(45f0, 1f0, 0.01f0, 100f0)
onany(cam.lookat, cam.eyeposition, cam.upvector) do lookat, eyepos, up
viewdir = 3f0 * normalize(eyepos - lookat)
scene.lights[1].position[] = viewdir
scene.camera.view[] = Makie.lookat(viewdir, Vec3(0.0), up)
return
end
notify(cam.lookat)
return
end
function update_camera!(ax::Axis3, phi, theta)
ax.azimuth[] = phi
ax.elevation[] = theta
return
end
function update_camera!(lscene::LScene, phi, theta)
cam = cameracontrols(lscene.scene)
dir = Vec3f(cos(theta) * cos(phi), cos(theta) * sin(phi), sin(theta))
cam.eyeposition[] = cam.lookat[] + norm(cam.eyeposition[] - cam.lookat[]) * dir
return
end
function reference_frame(parent, bbox = Rect2f(20, 20, 100, 100))
scene = Scene(
Makie.rootparent(parent.blockscene), px_area = bbox,
backgroundcolor = RGBAf(1,1,1,1), clear = true
)
m = gen_mesh()
mp = mesh!(scene, m, transparency = false)
tp = text!(scene,
1.05 .* Point3f[(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)],
text = ["-X", "X", "-Y", "Y", "-Z", "Z"],
align = (:center, :center),
rotation = [
Makie.qrotation(Vec3f(1, 0, 0), pi/2) * Makie.qrotation(Vec3f(0, 1, 0), -pi/2),
Makie.qrotation(Vec3f(1, 0, 0), -pi/2) * Makie.qrotation(Vec3f(0, 1, 0), pi/2),
Makie.qrotation(Vec3f(0, 1, 0), 0) * Makie.qrotation(Vec3f(1, 0, 0), -3pi/2),
Makie.qrotation(Vec3f(1, 0, 0), pi/2),
Makie.qrotation(Vec3f(1, 0, 0), pi),
Makie.qrotation(Vec3f(1, 0, 0), 0),
],
markerspace = :data, fontsize = 0.4, color = :white,
strokewidth = 0.1, strokecolor = :black, transparency = false
)
connect_camera!(parent, scene)
on(events(scene).mousebutton, priority = 20) do event
if event.button == Mouse.left && event.action == Mouse.press
p, idx = Makie.pick(scene)
if p === tp.plots[1]
phi, theta = [
(pi, 0), (pi, 0), (0, 0), (-pi/2, 0), (-pi/2, 0),
(pi/2, 0), (0, -pi/2), (0, -pi/2), (0, pi/2)
][idx]
update_camera!(parent, phi, theta)
elseif p === mp
face_idx = m.index[idx]
phi, theta = [
(-pi/2, -pi/2),
(3pi/4, -pi/4), (1pi/4, -pi/4), (7pi/4, -pi/4), (5pi/4, -pi/4),
(pi/2, -pi/4), (0, -pi/4), (-pi/2, -pi/4), (-pi, -pi/4),
(3pi/4, 0), (2pi/4, 0), (pi/4, 0), (0, 0),
(7pi/4, 0), (6pi/4, 0), (5pi/4, 0), (pi, 0),
(pi/2, pi/4), (pi, pi/4), (3pi/2, pi/4), (0, pi/4),
(5pi/4, pi/4), (7pi/4, pi/4), (pi/4, pi/4), (3pi/4, pi/4),
(-pi/2, pi/2)
][face_idx]
update_camera!(parent, phi, theta)
end
end
end
return scene
end This can be used with either fig = Figure()
ax = Axis3(fig[1,1])
p = scatter!(ax, rand(Point3f, 10))
display(fig)
scene = reference_frame(ax); fig = Figure()
lscene = LScene(fig[1,1])
p = scatter!(rand(Point3f, 10))
display(fig)
scene = reference_frame(lscene); |
This is awesome ! I think I'm gonna slightly alter the style but it's really nice ! Thanks for your help. Quick note : There is a |
Ha nice! I implemented something like this for the predecessor of GLMakie, which was still called GLVisualize: |
You did a lot already (and even more so) but here are some questions about this solution
|
If you want to make the visualization larger or smaller you can change the For an LScene you can probably just set |
You mean modifying the interaction as up0 = Vec3f(0,0,1) #0, 0, 1 for z direction up vector
onany(cam.lookat, cam.eyeposition, cam.upvector) do lookat, eyepos, up
viewdir = 3f0 * normalize(eyepos - lookat)
scene.lights[1].position[] = viewdir
scene.camera.view[] = Makie.lookat(viewdir, Vec3(0.0), up0) #? So that the sun scene follows the z direction
cam.upvector[] =up0#? So that the main scene follows the z direction
return
end I have tried something close to that but got a stackoverflow ^^ |
I was thinking here function update_camera!(lscene::LScene, phi, theta)
cam = cameracontrols(lscene.scene)
dir = Vec3f(cos(theta) * cos(phi), cos(theta) * sin(phi), sin(theta))
cam.eyeposition[] = cam.lookat[] + norm(cam.eyeposition[] - cam.lookat[]) * dir
return
end That would trigger only when you click on a face. |
Just for information, I wanted to put the "cube" in the top right corner so I used the widths=Vec2(200, 200)
root = Makie.rootparent(parent.blockscene)
on(events(root).window_area, priority=20) do event
scene.scene.px_area = Rect2i(event.widths - widths, widths)
end so that when the window is resized, the cube remains at the same position in the top right corner. Also while trying to lock the vertical axis I had to detect when function update_camera!(lscene::LScene, phi, theta)
cam = cameracontrols(lscene.scene)
dir = Vec3f(cos(theta) * cos(phi), cos(theta) * sin(phi), sin(theta))
cam.eyeposition[] = cam.lookat[] + norm(cam.eyeposition[] - cam.lookat[]) * dir
if theta≈ π / 2 || theta≈ -π / 2
cam.upvector[] = Vec3f(1, 0, 0)
else
cam.upvector[] = Vec3f(0, 0, 1)
end
return
end There are still some glitches though, as I get some |
@ffreyer Thanks again for all the help ! I will close this now, but maybe this could be added to some documentation or beautiful Makie fort instance. What do you think ? |
It is common in 3D CAD software to have a reference frame displayed in the picture
e.g. this in CATIA
Solidworks
It's fairly obvious that one can reproduce that in GLMakie with arrows, however, it can't figure how to have the arrow origin remain fixed in pixel space. Is there a way to achieve this. I'm guessing something quite equivalent to the
:pixe
oftext
would be nice.The arrow should however rotate according to the
:data
space and the camera anglesThe text was updated successfully, but these errors were encountered: