Skip to content

Commit

Permalink
Improve default near/far (#3293)
Browse files Browse the repository at this point in the history
* improve default near/far

* adjust near too
  • Loading branch information
ffreyer authored Oct 16, 2023
1 parent 8ac92a8 commit 722c659
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 42 deletions.
4 changes: 2 additions & 2 deletions docs/explanations/cameras.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ ez = Point3f(0,0,1)

fig = Figure()
scene = LScene(fig[1, 1])
cc = Makie.Camera3D(scene.scene, projectiontype = Makie.Perspective, far = 3.0)
cc = Makie.Camera3D(scene.scene, projectiontype = Makie.Perspective)

linesegments!(scene, Rect3f(Point3f(-1), Vec3f(2)), color = :black)
linesegments!(scene,
[-ex, ex, -ey, ey, -ez, ez],
color = [:red, :red, :green, :green, :blue, :blue]
)
center!(scene.scene)

cam = scene.scene.camera
eyeposition = cc.eyeposition
lookat = cc.lookat
Expand Down
111 changes: 71 additions & 40 deletions src/camera/camera3d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ struct Camera3D <: AbstractCamera3D
eyeposition::Observable{Vec3f}
lookat::Observable{Vec3f}
upvector::Observable{Vec3f}

# perspective projection matrix
fov::Observable{Float32}
near::Observable{Float32}
far::Observable{Float32}
bounding_sphere::Observable{Sphere{Float32}}
end

"""
Expand All @@ -37,6 +38,7 @@ Settings include anything that isn't a mouse or keyboard button.
- `fixed_axis = true`: If true panning uses the (world/plot) z-axis instead of the camera up direction.
- `zoom_shift_lookat = true`: If true keeps the data under the cursor when zooming.
- `cad = false`: If true rotates the view around `lookat` when zooming off-center.
- `clipping_mode = :bbox_relative`: Controls how `near` and `far` get processed. With `:static` they get passed as is, with `:view_relative` they get scaled by `norm(eyeposition - lookat)` and with `:bbox_relative` they get scaled to be just outside the scene bounding box. (More specifically `far = 1` is scaled to the furthest point of a bounding sphere and `near` is generally overwritten to be the closest point.)
- `keyboard_rotationspeed = 1f0` sets the speed of keyboard based rotations.
- `keyboard_translationspeed = 0.5f0` sets the speed of keyboard based translations.
Expand Down Expand Up @@ -91,11 +93,11 @@ Some keyword arguments are used to initialize fields. These include
- `upvector = Vec3f(0, 0, 1)`: The world direction corresponding to the up direction of the screen.
- `fov = 45.0` is the field of view. This is irrelevant if the camera uses an orthographic projection.
- `near = 0.1` sets the position of the near clip plane relative to `eyeposition - lookat`. Must be greater 0. Anything between the camera and the near clip plane is hidden.
- `far = 10.0` sets the position of the far clip plane relative to `eyeposition - lookat`. Anything further away than the far clip plane is hidden.
- `near = automatic` sets the position of the near clip plane. Anything between the camera and the near clip plane is hidden. Must be greater 0. Usage depends on `clipping_mode`.
- `far = automatic` sets the position of the far clip plane. Anything further away than the far clip plane is hidden. Usage depends on `clipping_mode`. Defaults to `1` for `clipping_mode = :bbox_relative`, `2` for `:view_relative` or a value derived from limits for `:static`.
Note that updating these observables in an active camera requires a call to `update_cam(scene)`
for them to be applied. For updating `eyeposition`, `lookat` and/or upvector
for them to be applied. For updating `eyeposition`, `lookat` and/or upvector
`update_cam!(scene, eyeposition, lookat, upvector = Vec3f(0,0,1))` is preferred.
The camera position and orientation can also be adjusted via the functions
Expand Down Expand Up @@ -150,20 +152,29 @@ function Camera3D(scene::Scene; kwargs...)
mouse_rotationspeed = 1f0,
mouse_translationspeed = 1f0,
mouse_zoomspeed = 1f0,

projectiontype = Makie.Perspective,
circular_rotation = (true, true, true),
rotation_center = :lookat,
update_rate = 1/30,
zoom_shift_lookat = true,
fixed_axis = true,
cad = false
cad = false,
clipping_mode = :bbox_relative
)

replace!(settings, :Camera3D, scene, overwrites)

if settings.clipping_mode[] === :view_relative
far_default = 2f0
elseif settings.clipping_mode[] === :bbox_relative
far_default = 1f0
else
far_default = 10f0 # will be set when inserting a plot
end

cam = Camera3D(
settings, controls,
settings, controls,

# Internals - controls
Observable(-1.0),
Expand All @@ -176,8 +187,9 @@ function Camera3D(scene::Scene; kwargs...)

# Semi-Internal - projection matrix
get(overwrites, :fov, Observable(45.0)),
get(overwrites, :near, Observable(0.01)),
get(overwrites, :far, Observable(100.0)),
get(overwrites, :near, Observable(0.1)),
get(overwrites, :far, Observable(far_default)),
Sphere(Point3f(0), 1f0)
)

disconnect!(camera(scene))
Expand All @@ -201,7 +213,7 @@ function Camera3D(scene::Scene; kwargs...)
:pan_left_key, :pan_right_key, :tilt_up_key, :tilt_down_key,
:roll_clockwise_key, :roll_counterclockwise_key
)

# Start ticking if relevant keys are pressed
on(camera(scene), events(scene).keyboardbutton) do event
if event.action in (Keyboard.press, Keyboard.repeat) && cam.pulser[] == -1.0 &&
Expand Down Expand Up @@ -257,7 +269,7 @@ end
"""
cam3d!(scene[; kwargs...])
Creates a `Camera3D` with `zoom_shift_lookat = true` and `fixed_axis = true`.
Creates a `Camera3D` with `zoom_shift_lookat = true` and `fixed_axis = true`.
For more information, see [`Camera3D``](@ref)
"""
cam3d!(scene; zoom_shift_lookat = true, fixed_axis = true, kwargs...) =
Expand All @@ -266,7 +278,7 @@ cam3d!(scene; zoom_shift_lookat = true, fixed_axis = true, kwargs...) =
"""
cam3d_cad!(scene[; kwargs...])
Creates a `Camera3D` with `cad = true`, `zoom_shift_lookat = false` and
Creates a `Camera3D` with `cad = true`, `zoom_shift_lookat = false` and
`fixed_axis = false`. For more information, see [`Camera3D``](@ref)
"""
cam3d_cad!(scene; cad = true, zoom_shift_lookat = false, fixed_axis = false, kwargs...) =
Expand Down Expand Up @@ -313,8 +325,8 @@ function on_pulse(scene, cam::Camera3D, timestep)
viewnorm = norm(cam.lookat[] - cam.eyeposition[])
xynorm = 2 * viewnorm * tand(0.5 * cam.fov[])
translation = keyboard_translationspeed * timestep * Vec3f(
xynorm * (right - left),
xynorm * (up - down),
xynorm * (right - left),
xynorm * (up - down),
viewnorm * (backward - forward)
)
else
Expand Down Expand Up @@ -377,7 +389,7 @@ end
function add_mouse_controls!(scene, cam::Camera3D)
@extract cam.controls (translation_button, rotation_button, reposition_button, scroll_mod)
@extract cam.settings (
mouse_translationspeed, mouse_rotationspeed, mouse_zoomspeed,
mouse_translationspeed, mouse_rotationspeed, mouse_zoomspeed,
cad, projectiontype, zoom_shift_lookat
)

Expand Down Expand Up @@ -422,7 +434,7 @@ function add_mouse_controls!(scene, cam::Camera3D)
dragging[] = (false, false)
translate_cam!(scene, cam, mouse_translationspeed[] .* Vec3f(diff[1], diff[2], 0f0))
consume = true
elseif dragging[][2]
elseif dragging[][2]
mousepos = mouseposition_px(scene)
dragging[] = (false, false)
rot_scaling = mouse_rotationspeed[] * (e.window_dpi[] * 0.005)
Expand All @@ -436,9 +448,9 @@ function add_mouse_controls!(scene, cam::Camera3D)
if ispressed(scene, reposition_button[], event.button) && is_mouseinside(scene)
plt, _, p = ray_assisted_pick(scene)
if p !== Point3f(NaN) && to_value(get(plt, :space, :data)) == :data && parent_scene(plt) == scene
# if translation/rotation happens with on-click reposition,
# if translation/rotation happens with on-click reposition,
# try uncommenting this
# dragging[] = (false, false)
# dragging[] = (false, false)
shift = p - cam.lookat[]
update_cam!(scene, cam, cam.eyeposition[] + shift, p)
end
Expand Down Expand Up @@ -493,10 +505,10 @@ end
"""
translate_cam!(scene, cam::Camera3D, v::Vec3)
Translates the camera by the given vector in camera space, i.e. by `v[1]` to
Translates the camera by the given vector in camera space, i.e. by `v[1]` to
the right, `v[2]` to the top and `v[3]` forward.
Note that this method reacts to `fix_x_key` etc. If any of those keys are
Note that this method reacts to `fix_x_key` etc. If any of those keys are
pressed the translation will be restricted to act in these directions.
"""
function translate_cam!(scene, cam::Camera3D, t::VecTypes)
Expand All @@ -508,12 +520,12 @@ end
"""
rotate_cam!(scene, cam::Camera3D, angles::Vec3)
Rotates the camera by the given `angles` around the camera x- (left, right),
y- (up, down) and z-axis (in out). The rotation around the y axis is applied
Rotates the camera by the given `angles` around the camera x- (left, right),
y- (up, down) and z-axis (in out). The rotation around the y axis is applied
first, then x, then y.
Note that this method reacts to `fix_x_key` etc and `fixed_axis`. The former
restrict the rotation around a specific axis when a given key is pressed. The
Note that this method reacts to `fix_x_key` etc and `fixed_axis`. The former
restrict the rotation around a specific axis when a given key is pressed. The
latter keeps the camera y axis fixed as the data space z axis.
"""
function rotate_cam!(scene, cam::Camera3D, angles::VecTypes, from_mouse=false)
Expand All @@ -530,10 +542,10 @@ zoom!(scene, zoom_step) = zoom!(scene, cameracontrols(scene), zoom_step, false,
Zooms the camera in or out based on the multiplier `zoom_step`. A `zoom_step`
of 1.0 is neutral, larger zooms out and lower zooms in.
If `cad = true` zooming will also apply a rotation based on how far the cursor
is from the center of the scene. If `zoom_shift_lookat = true` and
If `cad = true` zooming will also apply a rotation based on how far the cursor
is from the center of the scene. If `zoom_shift_lookat = true` and
`projectiontype = Orthographic` zooming will keep the data under the cursor at
the same screen space position.
the same screen space position.
"""
function zoom!(scene, cam::Camera3D, zoom_step, cad = false, zoom_shift_lookat = false)
_zoom!(scene, cam, zoom_step, cad, zoom_shift_lookat)
Expand Down Expand Up @@ -594,7 +606,7 @@ function _rotate_cam!(scene, cam::Camera3D, angles::VecTypes, from_mouse=false)
rotation = Quaternionf(0, 0, 0, 1)
if !xor(fix_x, fix_y, fix_z)
# if there are more or less than one restriction apply all rotations
# Note that the y rotation needs to happen first here so that
# Note that the y rotation needs to happen first here so that
# fixed_axis = true actually keeps the the axis fixed.
rotation *= qrotation(y_axis, angles[2])
rotation *= qrotation(x_axis, angles[1])
Expand Down Expand Up @@ -643,7 +655,7 @@ function _zoom!(scene, cam::Camera3D, zoom_step, cad = false, zoom_shift_lookat
lookat = cam.lookat[]
eyepos = cam.eyeposition[]
viewdir = lookat - eyepos # -z

if cad
# Rotate view based on offset from center
u_z = normalize(viewdir)
Expand All @@ -664,12 +676,12 @@ function _zoom!(scene, cam::Camera3D, zoom_step, cad = false, zoom_shift_lookat
ws = widths(scene.px_area[])
rel_pos = (2.0 .* mouseposition_px(scene) .- ws) ./ ws[2]
shift = (1 - zoom_step) * (rel_pos[1] * u_x + rel_pos[2] * u_y)

if cam.settings.projectiontype[] == Makie.Orthographic
scale = norm(viewdir)
else
# With perspective projection depth scales shift, but there is no way
# to tell which depth the user may want to keep in view. So we just
# to tell which depth the user may want to keep in view. So we just
# assume it's the same depth as "lookat".
scale = norm(viewdir) * tand(0.5 * cam.fov[])
end
Expand All @@ -692,17 +704,29 @@ end

# Update camera matrices
function update_cam!(scene::Scene, cam::Camera3D)
@extractvalue cam (lookat, eyeposition, upvector, near, far, fov)
@extractvalue cam (lookat, eyeposition, upvector, near, far, fov, bounding_sphere)

view = Makie.lookat(eyeposition, lookat, upvector)

if cam.settings.clipping_mode[] === :view_relative
view_dist = norm(eyeposition - lookat)
near = view_dist * near; far = view_dist * far
elseif cam.settings.clipping_mode[] === :bbox_relative
view_dist = norm(eyeposition - lookat)
center_dist = norm(eyeposition - origin(bounding_sphere))
far_dist = center_dist + radius(bounding_sphere)
near = max(view_dist * near, center_dist - radius(bounding_sphere))
far = far_dist * far
elseif cam.settings.clipping_mode[] !== :static
@error "clipping_mode = $(cam.settings.clipping_mode[]) not recognized, using :static."
end

aspect = Float32((/)(widths(scene.px_area[])...))
if cam.settings.projectiontype[] == Makie.Perspective
view_norm = norm(eyeposition - lookat)
proj = perspectiveprojection(fov, aspect, view_norm * near, view_norm * far)
proj = perspectiveprojection(fov, aspect, near, far)
else
h = norm(eyeposition - lookat); w = h * aspect
proj = orthographicprojection(-w, w, -h, h, h * near, h * far)
proj = orthographicprojection(-w, w, -h, h, near, far)
end

set_proj_view!(camera(scene), proj, view)
Expand All @@ -715,19 +739,26 @@ end
function update_cam!(scene::Scene, cam::Camera3D, area3d::Rect)
bb = Rect3f(area3d)
width = widths(bb)
center = maximum(bb) - 0.5 * width
center = maximum(bb) - 0.5f0 * width
radius = 0.5f0 * norm(width)
cam.bounding_sphere[] = Sphere(Point3f(center), radius)

old_dir = normalize(cam.eyeposition[] .- cam.lookat[])
if cam.settings.projectiontype[] == Makie.Perspective
dist = 0.5 * norm(width) / tand(0.5 * cam.fov[])
dist = radius / tand(0.5f0 * cam.fov[])
else
dist = 0.5 * norm(width)
dist = radius
end

cam.lookat[] = center
cam.eyeposition[] = cam.lookat[] .+ dist * old_dir
cam.upvector[] = Vec3f(0, 0, 1) # Should we reset this?

if cam.settings.clipping_mode[] === :static
cam.near[] = 0.1f0 * dist
cam.far[] = 2f0 * dist
end

update_cam!(scene, cam)

return
Expand All @@ -748,10 +779,10 @@ update_cam!(scene::Scene, args::Real...) = update_cam!(scene, cameracontrols(sce
update_cam!(scene, cam::Camera3D, ϕ, θ[, radius])
Set the camera position based on two angles `0 ≤ ϕ ≤ 2π` and `-pi/2 ≤ θ ≤ pi/2`
and an optional radius around the current `cam.lookat[]`.
and an optional radius around the current `cam.lookat[]`.
"""
function update_cam!(
scene::Scene, camera::Camera3D, phi::Real, theta::Real,
scene::Scene, camera::Camera3D, phi::Real, theta::Real,
radius::Real = norm(camera.eyeposition[] - camera.lookat[]),
center = camera.lookat[]
)
Expand Down

0 comments on commit 722c659

Please sign in to comment.