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
|
use ecs::component::local::Local;
use ecs::sole::Single;
use ecs::system::{Into, System};
use ecs::{Component, Query};
use glfw::window::{Key, KeyState};
use crate::camera::Camera;
use crate::delta_time::DeltaTime;
use crate::event::Update as UpdateEvent;
use crate::input::{Cursor, CursorFlags, Keys};
use crate::transform::Position;
use crate::vector::{Vec2, Vec3};
/// Fly camera extension.
pub struct Extension(pub Options);
impl ecs::extension::Extension for Extension
{
fn collect(self, mut collector: ecs::extension::Collector<'_>)
{
collector.add_system(
UpdateEvent,
update.into_system().initialize((
CursorState {
current_yaw: -90.0,
..Default::default()
},
self.0,
)),
);
}
}
#[derive(Debug, Component)]
pub struct Options
{
pub camera_speed: f32,
pub mouse_sensitivity: f32,
}
fn update(
camera_query: Query<(Camera, Position)>,
keys: Single<Keys>,
cursor: Single<Cursor>,
cursor_flags: Single<CursorFlags>,
delta_time: Single<DeltaTime>,
mut cursor_state: Local<CursorState>,
options: Local<Options>,
)
{
let Some((mut camera, mut camera_pos)) = camera_query.iter().next() else {
#[cfg(feature = "debug")]
tracing::warn!("No camera");
return;
};
if cursor.has_moved && cursor_flags.is_first_move.flag {
println!("First cursor move");
cursor_state.last_pos = cursor.position;
}
let delta_time = delta_time.duration;
let mut x_offset = cursor.position.x - cursor_state.last_pos.x;
let mut y_offset = cursor_state.last_pos.y - cursor.position.y;
cursor_state.last_pos = cursor.position;
x_offset *= f64::from(options.mouse_sensitivity);
y_offset *= f64::from(options.mouse_sensitivity);
cursor_state.current_yaw += x_offset;
cursor_state.current_pitch += y_offset;
cursor_state.current_pitch = cursor_state.current_pitch.clamp(-89.0, 89.0);
// TODO: This casting to a f32 from a f64 is horrible. fix it
#[allow(clippy::cast_possible_truncation)]
let direction = Vec3 {
x: (cursor_state.current_yaw.to_radians().cos()
* cursor_state.current_pitch.to_radians().cos()) as f32,
y: cursor_state.current_pitch.to_radians().sin() as f32,
z: (cursor_state.current_yaw.to_radians().sin()
* cursor_state.current_pitch.to_radians().cos()) as f32,
}
.normalize();
let cam_right = direction.cross(&Vec3::UP).normalize();
camera.global_up = cam_right.cross(&direction).normalize();
if matches!(keys.get_key_state(Key::W), KeyState::Pressed) {
camera_pos.position +=
direction * options.camera_speed * delta_time.as_secs_f32();
}
if matches!(keys.get_key_state(Key::S), KeyState::Pressed) {
camera_pos.position -=
direction * options.camera_speed * delta_time.as_secs_f32();
}
if matches!(keys.get_key_state(Key::A), KeyState::Pressed) {
let cam_left = -direction.cross(&Vec3::UP).normalize();
camera_pos.position += cam_left * options.camera_speed * delta_time.as_secs_f32();
}
if matches!(keys.get_key_state(Key::D), KeyState::Pressed) {
let cam_right = direction.cross(&Vec3::UP).normalize();
camera_pos.position +=
cam_right * options.camera_speed * delta_time.as_secs_f32();
}
camera.target = camera_pos.position + direction;
}
#[derive(Debug, Component)]
struct CursorState
{
last_pos: Vec2<f64>,
current_pitch: f64,
current_yaw: f64,
}
impl Default for CursorState
{
fn default() -> Self
{
Self {
last_pos: Vec2::default(),
current_pitch: 0.0,
current_yaw: 0.0,
}
}
}
|