sim_core/
camera.rs

1use bevy::input::mouse::MouseMotion;
2use bevy::prelude::*;
3
4#[derive(Component)]
5pub struct Flycam {
6    pub yaw: f32,
7    pub pitch: f32,
8    pub speed: f32,
9    pub mouse_sensitivity: f32,
10}
11
12#[derive(Component)]
13pub struct ProbePovCamera;
14
15#[derive(Resource, Default)]
16pub struct PovState {
17    pub use_probe: bool,
18}
19
20#[derive(Component)]
21pub struct UiOverlayCamera;
22
23pub fn setup_camera(mut commands: Commands) {
24    let transform = Transform::from_xyz(-6.0, 4.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y);
25    let (yaw, pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
26
27    commands.spawn((
28        Camera3d::default(),
29        Camera::default(),
30        transform,
31        Flycam {
32            yaw,
33            pitch,
34            speed: 5.0,
35            mouse_sensitivity: 0.0025,
36        },
37    ));
38
39    // Dedicated UI camera so HUD remains visible regardless of active 3D camera.
40    commands.spawn((
41        UiOverlayCamera,
42        Camera2d,
43        Camera {
44            is_active: true,
45            order: 10,
46            ..default()
47        },
48    ));
49}
50
51pub fn camera_controller(
52    time: Res<Time>,
53    keys: Res<ButtonInput<KeyCode>>,
54    mouse_buttons: Res<ButtonInput<MouseButton>>,
55    mut mouse_motion: MessageReader<MouseMotion>,
56    mut query: Query<(&mut Transform, &mut Flycam)>,
57) {
58    let dt = time.delta_secs();
59
60    for (mut transform, mut cam) in &mut query {
61        if mouse_buttons.pressed(MouseButton::Right) {
62            let mut delta = Vec2::ZERO;
63            for ev in mouse_motion.read() {
64                delta += ev.delta;
65            }
66            cam.yaw -= delta.x * cam.mouse_sensitivity;
67            cam.pitch -= delta.y * cam.mouse_sensitivity;
68            cam.pitch = cam.pitch.clamp(-1.54, 1.54);
69        } else {
70            // Clear any accumulated motion if mouse not held.
71            mouse_motion.clear();
72        }
73
74        let yaw = cam.yaw;
75        let pitch = cam.pitch;
76        let rot = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.0);
77        // Camera-relative axes (true free-fly; no world-up lock).
78        // Use -Z as forward to align with Bevy's camera look direction.
79        let forward = rot * -Vec3::Z;
80        let right = rot * Vec3::X;
81        let up = rot * Vec3::Y;
82
83        let mut velocity = Vec3::ZERO;
84        if keys.pressed(KeyCode::KeyW) || keys.pressed(KeyCode::ArrowUp) {
85            velocity += forward;
86        }
87        if keys.pressed(KeyCode::KeyS) || keys.pressed(KeyCode::ArrowDown) {
88            velocity -= forward;
89        }
90        if keys.pressed(KeyCode::KeyD) || keys.pressed(KeyCode::ArrowRight) {
91            velocity += right;
92        }
93        if keys.pressed(KeyCode::KeyA) || keys.pressed(KeyCode::ArrowLeft) {
94            velocity -= right;
95        }
96        if keys.pressed(KeyCode::Space) {
97            velocity += up;
98        }
99        if keys.pressed(KeyCode::ShiftLeft) {
100            velocity -= up;
101        }
102
103        if velocity.length_squared() > 0.0 {
104            transform.translation += velocity.normalize() * cam.speed * dt;
105        }
106
107        transform.rotation = rot;
108    }
109}
110
111pub fn pov_toggle_system(
112    keys: Res<ButtonInput<KeyCode>>,
113    mut state: ResMut<PovState>,
114    mut free_cams: Query<&mut Camera, (With<Flycam>, Without<ProbePovCamera>)>,
115    mut probe_cams: Query<&mut Camera, With<ProbePovCamera>>,
116) {
117    if !keys.just_pressed(KeyCode::KeyC) {
118        return;
119    }
120
121    state.use_probe = !state.use_probe;
122    let use_probe = state.use_probe;
123    let use_free = !use_probe;
124
125    for mut cam in &mut free_cams {
126        cam.is_active = use_free;
127    }
128    for mut cam in &mut probe_cams {
129        cam.is_active = use_probe;
130    }
131}