1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Configurable options for the challenge of working with orthographic cameras.
use bevy_camera::prelude::*;
use bevy_ecs::prelude::*;
use bevy_math::{DQuat, DVec3};
use bevy_reflect::prelude::*;
use crate::prelude::*;
use self::motion::CurrentMotion;
/// Settings used when the [`EditorCam`] has a perspective [`Projection`].
#[derive(Debug, Clone, Reflect)]
pub struct PerspectiveSettings {
/// Limits the near clipping plane to always fit inside this range.
///
/// The camera controller will try to make the near clipping plane smaller when you zoom in to
/// ensure the anchor (the thing you are zooming into) is always within the view frustum
/// (visible), bounded by this limit.
///
/// Unless the camera is zoomed very close to something, it will spend most of the time at the
/// high end of this limit - you should treat that like the default near clipping plane. Bevy
/// defaults to `0.1`, and you should probably use that too unless you have a very good reason
/// not to. Many rendering effects that rely on depth can break down if the clipping plane is
/// very far from `0.1`.
pub near_clip_limits: std::ops::Range<f32>,
/// When computing the near plane position, the anchor depth is multiplied by this value to
/// determine the new near clip position. This should be smaller than one, to ensure that the
/// object you are looking at, which will be located at the anchor position, is bot being
/// clipped. Some parts of the object may protrude toward the camera, which is what necessitates
/// this.
pub near_clip_multiplier: f32,
}
impl Default for PerspectiveSettings {
fn default() -> Self {
Self {
near_clip_limits: 1e-9..f32::INFINITY,
near_clip_multiplier: 0.05,
}
}
}
/// Updates perspective projection properties of editor cameras.
pub fn update_perspective(mut cameras: Query<(&EditorCam, Mut<Projection>)>) {
for (editor_cam, mut projection) in cameras.iter_mut() {
let Projection::Perspective(ref mut perspective) = *projection else {
continue;
};
let limits = editor_cam.perspective.near_clip_limits.clone();
let multiplier = editor_cam.perspective.near_clip_multiplier;
perspective.near = (editor_cam.last_anchor_depth.abs() as f32 * multiplier)
.clamp(limits.start, limits.end);
}
}
/// Settings used when the [`EditorCam`] has an orthographic [`Projection`].
#[derive(Debug, Clone, Reflect)]
pub struct OrthographicSettings {
/// The camera's near clipping plane will move closer and farther from the anchor point during
/// zoom to maximize precision. The position of the near plane is based on the orthographic
/// projection `scale`, multiplied by this value.
///
/// To maximize depth precision, make this as small ap possible. If the value is too large,
/// depth-based effects like SSAO will break down. If the value is too small, objects that
/// should be visible will be clipped. Ideally, the clipping planes should scale with the scene
/// geometry and camera frustum to tightly bound the visible scene, but this is not yet
/// implemented.
pub scale_to_near_clip: f32,
/// Limits the distance the near clip plane can be to the anchor. The low limit is useful to
/// prevent geometry clipping when zooming in, while the high limit is useful to prevent the
/// camera moving too far away from the anchor, causing precision issues.
pub near_clip_limits: std::ops::Range<f32>,
/// The far plane is placed opposite the anchor from the near plane, at this multiple of the
/// distance from the near plane to the anchor. Setting this to 1.0 means the camera frustum is
/// centered on the anchor. It might be desirable to make this larger to prevent things in the
/// background from disappearing when zooming in.
pub far_clip_multiplier: f32,
}
impl Default for OrthographicSettings {
fn default() -> Self {
Self {
scale_to_near_clip: 1_000_000.0,
near_clip_limits: 1.0..1_000_000.0,
far_clip_multiplier: 1.0,
}
}
}
/// Update the ortho camera projection and position based on the [`OrthographicSettings`].
pub fn update_orthographic(
mut camera_set: ParamSet<(
Query<(Entity, &mut EditorCam, Mut<Projection>)>,
Query<EntityMut, With<EditorCam>>,
)>,
transform_adapter: Res<TransformAdapter>,
) {
camera_set
.p0()
.iter_mut()
.filter_map(|(entity, mut editor_cam, mut projection)| {
if let Projection::Orthographic(ref mut orthographic) = *projection {
let mut delta_translation = DVec3::ZERO;
let anchor_dist = editor_cam.last_anchor_depth().abs() as f32;
let target_dist = (editor_cam.orthographic.scale_to_near_clip * orthographic.scale)
.clamp(
editor_cam.orthographic.near_clip_limits.start,
editor_cam.orthographic.near_clip_limits.end,
);
let forward_amount = anchor_dist - target_dist;
let cam_forward = DVec3::NEG_Z;
let movement = cam_forward * forward_amount as f64;
if movement != DVec3::ZERO {
delta_translation += movement;
}
editor_cam.last_anchor_depth += forward_amount as f64;
if let CurrentMotion::UserControlled { ref mut anchor, .. } =
editor_cam.current_motion
{
anchor.z += forward_amount as f64;
}
orthographic.near = 0.0;
orthographic.far =
anchor_dist * (1.0 + editor_cam.orthographic.far_clip_multiplier);
Some((entity, delta_translation))
} else {
None
}
})
.collect::<Vec<_>>()
.iter()
.for_each(|(entity, delta_translation)| {
if let Ok(mut entity_mut) = camera_set.p1().get_mut(*entity) {
transform_adapter.apply_delta(&mut entity_mut, *delta_translation, DQuat::IDENTITY);
}
});
}