honeycomb_render/render/
camera.rs1use bevy::input::mouse::{MouseMotion, MouseWheel};
2use bevy::math::vec2;
3use bevy::prelude::*;
4use bevy::window::PrimaryWindow;
5
6use crate::gui::{CustomTab, UiState};
7
8#[derive(Component)]
11pub struct PanOrbitCamera {
12 pub(crate) focus: Vec3,
13 pub(crate) radius: f32,
14 pub(crate) upside_down: bool,
15}
16
17impl Default for PanOrbitCamera {
18 fn default() -> Self {
19 Self {
20 focus: Vec3::ZERO,
21 radius: 5.0,
22 upside_down: false,
23 }
24 }
25}
26
27pub fn update_camera(
29 window_q: Query<&Window>,
30 mut ev_motion: EventReader<MouseMotion>,
31 mut ev_scroll: EventReader<MouseWheel>,
32 input_mouse: Res<ButtonInput<MouseButton>>,
33 mut query: Query<(&mut PanOrbitCamera, &mut Transform, &Projection)>,
34) {
35 let window = window_q.single();
36 let orbit_button = MouseButton::Right;
38 let pan_button = MouseButton::Left;
39
40 let mut pan = Vec2::ZERO;
41 let mut rotation_move = Vec2::ZERO;
42 let mut scroll = 0.0;
43 let mut orbit_button_changed = false;
44
45 if input_mouse.pressed(orbit_button) {
46 for ev in ev_motion.read() {
47 rotation_move += ev.delta;
48 }
49 } else if input_mouse.pressed(pan_button) {
50 for ev in ev_motion.read() {
52 pan += ev.delta * 2.;
53 }
54 }
55
56 for ev in ev_scroll.read() {
57 scroll += ev.y;
58
59 scroll /= if cfg!(target_arch = "wasm32") {
60 100.0
61 } else {
62 20.0
63 };
64 }
65 if input_mouse.just_released(orbit_button) || input_mouse.just_pressed(orbit_button) {
66 orbit_button_changed = true;
67 }
68
69 for (mut pan_orbit, mut transform, projection) in &mut query {
70 if orbit_button_changed {
71 let up = transform.rotation * Vec3::Y;
74 pan_orbit.upside_down = up.y <= 0.0;
75 }
76 let window = vec2(
77 window.physical_width() as f32,
78 window.physical_height() as f32,
79 );
80
81 let mut any = false;
82 if rotation_move.length_squared() > 0.0 {
83 any = true;
84 let delta_x = {
85 let delta = rotation_move.x / window.x * std::f32::consts::PI * 2.0;
86 if pan_orbit.upside_down {
87 -delta
88 } else {
89 delta
90 }
91 };
92 let delta_y = rotation_move.y / window.y * std::f32::consts::PI;
93 let yaw = Quat::from_rotation_y(-delta_x);
94 let pitch = Quat::from_rotation_x(-delta_y);
95 transform.rotation = yaw * transform.rotation; transform.rotation *= pitch; } else if pan.length_squared() > 0.0 {
98 any = true;
99
100 if let Projection::Perspective(projection) = projection {
101 pan *= Vec2::new(projection.fov * projection.aspect_ratio, projection.fov) / window;
102 }
103 let right = transform.rotation * Vec3::X * -pan.x;
105 let up = transform.rotation * Vec3::Y * pan.y;
106 let translation = (right + up) * pan_orbit.radius;
108 pan_orbit.focus += translation;
109 } else if scroll.abs() > 0.0 {
110 any = true;
111 pan_orbit.radius -= scroll * pan_orbit.radius * 0.2;
112 pan_orbit.radius = f32::max(pan_orbit.radius, 0.05);
114 }
115
116 if any {
117 let rot_matrix = Mat3::from_quat(transform.rotation);
118 transform.translation =
119 pan_orbit.focus + rot_matrix.mul_vec3(Vec3::new(0.0, 0.0, pan_orbit.radius));
120 }
121 }
122
123 ev_motion.clear();
124}
125
126#[allow(clippy::missing_panics_doc)]
127pub fn cursor_in_render(
131 q_windows: Query<&Window, With<PrimaryWindow>>,
132 ui_state: Res<UiState>,
133) -> bool {
134 if let Some(position) = q_windows.single().cursor_position() {
136 let tree = ui_state.tree();
137 if let Some((node_idx, _)) = tree.find_tab(&CustomTab::Render) {
138 let rect = &tree[node_idx].rect().expect("unreachable");
139 return rect.contains(position.to_array().into());
140 }
141 return false;
142 }
143 false
144}