1use crate::math::{Vec2, Vec3, Rect, Mat4, Vec2Ext};
11
12#[derive(Debug, Clone)]
17pub struct Camera {
18 pub position: Vec2,
20 pub rotation: f32,
22 pub zoom: f32,
24 pub min_zoom: f32,
26 pub max_zoom: f32,
28 follow_target: Option<Vec2>,
30 follow_lerp: f32,
32 follow_deadzone: f32,
34 shake: CameraShake,
36 viewport: Option<Rect>,
38 layers: u32,
40 z_order: i32,
42}
43
44#[derive(Debug, Clone, Default)]
46struct CameraShake {
47 intensity: f32,
48 duration: f32,
49 remaining: f32,
50 offset: Vec2,
51}
52
53impl Camera {
54 pub fn new(x: f32, y: f32) -> Self {
56 Self {
57 position: Vec2::new(x, y),
58 rotation: 0.0,
59 zoom: 1.0,
60 min_zoom: 0.01,
61 max_zoom: 100.0,
62 follow_target: None,
63 follow_lerp: 0.1,
64 follow_deadzone: 0.0,
65 shake: CameraShake::default(),
66 viewport: None,
67 layers: u32::MAX, z_order: 0,
69 }
70 }
71
72 pub fn centered() -> Self {
74 Self::new(0.0, 0.0)
75 }
76
77 pub fn set_zoom(&mut self, zoom: f32) {
79 self.zoom = zoom.clamp(self.min_zoom, self.max_zoom);
80 }
81
82 pub fn set_zoom_limits(&mut self, min: f32, max: f32) {
84 self.min_zoom = min.min(max);
85 self.max_zoom = max.max(min);
86 self.zoom = self.zoom.clamp(self.min_zoom, self.max_zoom);
87 }
88
89 pub fn follow(&mut self, target: Vec2, lerp_speed: f32) {
91 self.follow_target = Some(target);
92 self.follow_lerp = lerp_speed;
93 }
94
95 pub fn set_follow_deadzone(&mut self, deadzone: f32) {
97 self.follow_deadzone = deadzone;
98 }
99
100 pub fn stop_following(&mut self) {
102 self.follow_target = None;
103 }
104
105 pub fn shake(&mut self, intensity: f32, duration: f32) {
110 self.shake.intensity = intensity;
111 self.shake.duration = duration;
112 self.shake.remaining = duration;
113 }
114
115 pub fn set_viewport(&mut self, viewport: Rect) {
119 self.viewport = Some(viewport);
120 }
121
122 pub fn clear_viewport(&mut self) {
124 self.viewport = None;
125 }
126
127 pub fn set_layers(&mut self, layers: u32) {
129 self.layers = layers;
130 }
131
132 pub fn set_z_order(&mut self, z: i32) {
134 self.z_order = z;
135 }
136
137 pub fn update(&mut self, dt: f32) {
141 if let Some(target) = self.follow_target {
143 let diff = target - self.position;
144 let dist = diff.length();
145 if dist > self.follow_deadzone {
146 self.position = self.position.move_toward(target, dist * self.follow_lerp);
147 }
148 }
149
150 if self.shake.remaining > 0.0 {
152 self.shake.remaining -= dt;
153 let t = self.shake.remaining / self.shake.duration;
154 let current_intensity = self.shake.intensity * t;
155 let angle = quad_rand::gen_range(0.0, std::f32::consts::TAU);
156 self.shake.offset = Vec2::new(
157 angle.cos() * current_intensity,
158 angle.sin() * current_intensity,
159 );
160 } else {
161 self.shake.offset = Vec2::ZERO;
162 }
163 }
164
165 pub fn view_matrix(&self, screen_size: Vec2) -> Mat4 {
169 let offset = self.position + self.shake.offset;
171 let mut view = Mat4::from_translation(Vec3::new(
172 -offset.x,
173 -offset.y,
174 0.0,
175 ));
176
177 if self.rotation.abs() > 1e-6 {
179 view = Mat4::from_rotation_z(-self.rotation) * view;
180 }
181
182 if (self.zoom - 1.0).abs() > 1e-6 {
184 let scale = Mat4::from_scale(Vec3::new(self.zoom, self.zoom, 1.0));
185 let to_center = Mat4::from_translation(Vec3::new(
186 screen_size.x * 0.5,
187 screen_size.y * 0.5,
188 0.0,
189 ));
190 let from_center = Mat4::from_translation(Vec3::new(
191 -screen_size.x * 0.5,
192 -screen_size.y * 0.5,
193 0.0,
194 ));
195 view = to_center * scale * from_center * view;
196 }
197
198 view
199 }
200
201 pub fn screen_to_world(&self, screen_pos: Vec2, screen_size: Vec2) -> Vec2 {
203 let center = screen_size * 0.5;
204 let mut world = (screen_pos - center) / self.zoom;
205
206 if self.rotation.abs() > 1e-6 {
208 world = world.rotated(-self.rotation);
209 }
210
211 world + self.position + self.shake.offset
212 }
213
214 pub fn world_to_screen(&self, world_pos: Vec2, screen_size: Vec2) -> Vec2 {
216 let center = screen_size * 0.5;
217 let relative = world_pos - self.position - self.shake.offset;
218
219 let rotated = if self.rotation.abs() > 1e-6 {
220 relative.rotated(self.rotation)
221 } else {
222 relative
223 };
224
225 rotated * self.zoom + center
226 }
227
228 pub fn visible_rect(&self, screen_size: Vec2) -> Rect {
230 let half_extents = screen_size / (2.0 * self.zoom);
231 Rect::centered(self.position, half_extents * 2.0)
232 }
233}