1use crate::{
4 get_context,
5 math::Rect,
6 prelude::RenderPass,
7 texture::RenderTarget,
8 window::{screen_height, screen_width},
9};
10use glam::{vec2, vec3, Mat4, Vec2, Vec3};
11
12pub trait Camera {
13 fn matrix(&self) -> Mat4;
14 fn depth_enabled(&self) -> bool;
15 fn render_pass(&self) -> Option<RenderPass>;
16 fn viewport(&self) -> Option<(i32, i32, i32, i32)>;
17}
18
19#[derive(Debug)]
20pub struct Camera2D {
21 pub rotation: f32,
23 pub zoom: Vec2,
25 pub target: Vec2,
27 pub offset: Vec2,
29
30 pub render_target: Option<RenderTarget>,
34
35 pub viewport: Option<(i32, i32, i32, i32)>,
43}
44
45impl Camera2D {
46 pub fn from_display_rect(rect: Rect) -> Camera2D {
48 let target = vec2(rect.x + rect.w / 2., rect.y + rect.h / 2.);
49
50 Camera2D {
51 target,
52 zoom: vec2(1. / rect.w * 2., -1. / rect.h * 2.),
53 offset: vec2(0., 0.),
54 rotation: 0.,
55
56 render_target: None,
57 viewport: None,
58 }
59 }
60}
61
62impl Default for Camera2D {
63 fn default() -> Camera2D {
64 Camera2D {
65 zoom: vec2(1., 1.),
66 offset: vec2(0., 0.),
67 target: vec2(0., 0.),
68 rotation: 0.,
69
70 render_target: None,
71 viewport: None,
72 }
73 }
74}
75
76impl Camera for Camera2D {
77 fn matrix(&self) -> Mat4 {
78 let mat_origin = Mat4::from_translation(vec3(-self.target.x, -self.target.y, 0.0));
95 let mat_rotation = Mat4::from_axis_angle(vec3(0.0, 0.0, 1.0), self.rotation.to_radians());
96 let invert_y = if self.render_target.is_some() {
97 1.0
98 } else {
99 -1.0
100 };
101 let mat_scale = Mat4::from_scale(vec3(self.zoom.x, self.zoom.y * invert_y, 1.0));
102 let mat_translation = Mat4::from_translation(vec3(self.offset.x, self.offset.y, 0.0));
103
104 mat_translation * ((mat_scale * mat_rotation) * mat_origin)
105 }
106
107 fn depth_enabled(&self) -> bool {
108 false
109 }
110
111 fn render_pass(&self) -> Option<RenderPass> {
112 self.render_target.as_ref().map(|rt| rt.render_pass.clone())
113 }
114
115 fn viewport(&self) -> Option<(i32, i32, i32, i32)> {
116 self.viewport
117 }
118}
119
120impl Camera2D {
121 pub fn world_to_screen(&self, point: Vec2) -> Vec2 {
126 let dims = self
127 .viewport()
128 .map(|(vx, vy, vw, vh)| Rect {
129 x: vx as f32,
130 y: screen_height() - (vy + vh) as f32,
131 w: vw as f32,
132 h: vh as f32,
133 })
134 .unwrap_or(Rect {
135 x: 0.0,
136 y: 0.0,
137 w: screen_width(),
138 h: screen_height(),
139 });
140
141 let mat = self.matrix();
142 let transform = mat.transform_point3(vec3(point.x, point.y, 0.));
143
144 vec2(
145 (transform.x / 2. + 0.5) * dims.w + dims.x,
146 (0.5 - transform.y / 2.) * dims.h + dims.y,
147 )
148 }
149
150 pub fn screen_to_world(&self, point: Vec2) -> Vec2 {
154 let dims = self
155 .viewport()
156 .map(|(vx, vy, vw, vh)| Rect {
157 x: vx as f32,
158 y: screen_height() - (vy + vh) as f32,
159 w: vw as f32,
160 h: vh as f32,
161 })
162 .unwrap_or(Rect {
163 x: 0.0,
164 y: 0.0,
165 w: screen_width(),
166 h: screen_height(),
167 });
168
169 let point = vec2(
170 (point.x - dims.x) / dims.w * 2. - 1.,
171 1. - (point.y - dims.y) / dims.h * 2.,
172 );
173 let inv_mat = self.matrix().inverse();
174 let transform = inv_mat.transform_point3(vec3(point.x, point.y, 0.));
175
176 vec2(transform.x, transform.y)
177 }
178}
179
180#[derive(Debug, Clone, Copy)]
181pub enum Projection {
182 Perspective,
183 Orthographics,
184}
185
186#[derive(Debug)]
187pub struct Camera3D {
188 pub position: Vec3,
190 pub target: Vec3,
192 pub up: Vec3,
194 pub fovy: f32,
197 pub aspect: Option<f32>,
201 pub projection: Projection,
203
204 pub render_target: Option<RenderTarget>,
208
209 pub viewport: Option<(i32, i32, i32, i32)>,
217
218 pub z_near: f32,
220 pub z_far: f32,
222}
223
224impl Default for Camera3D {
225 fn default() -> Camera3D {
226 Camera3D {
227 position: vec3(0., -10., 0.),
228 target: vec3(0., 0., 0.),
229 aspect: None,
230 up: vec3(0., 0., 1.),
231 fovy: 45.0_f32.to_radians(),
232 projection: Projection::Perspective,
233 render_target: None,
234 viewport: None,
235 z_near: 0.01,
236 z_far: 10000.0,
237 }
238 }
239}
240
241impl Camera for Camera3D {
242 fn matrix(&self) -> Mat4 {
243 let aspect = self.aspect.unwrap_or(screen_width() / screen_height());
244
245 match self.projection {
246 Projection::Perspective => {
247 Mat4::perspective_rh_gl(self.fovy, aspect, self.z_near, self.z_far)
248 * Mat4::look_at_rh(self.position, self.target, self.up)
249 }
250 Projection::Orthographics => {
251 let top = self.fovy / 2.0;
252 let right = top * aspect;
253
254 Mat4::orthographic_rh_gl(-right, right, -top, top, self.z_near, self.z_far)
255 * Mat4::look_at_rh(self.position, self.target, self.up)
256 }
257 }
258 }
259
260 fn depth_enabled(&self) -> bool {
261 true
262 }
263
264 fn render_pass(&self) -> Option<RenderPass> {
265 self.render_target.as_ref().map(|rt| rt.render_pass.clone())
266 }
267
268 fn viewport(&self) -> Option<(i32, i32, i32, i32)> {
269 self.viewport
270 }
271}
272
273pub fn set_camera(camera: &dyn Camera) {
275 let context = get_context();
276
277 context.perform_render_passes();
279
280 context
281 .gl
282 .render_pass(camera.render_pass().map(|rt| rt.raw_miniquad_id()));
283
284 context.gl.viewport(camera.viewport());
285 context.gl.depth_test(camera.depth_enabled());
286 context.camera_matrix = Some(camera.matrix());
287}
288
289pub fn set_default_camera() {
291 let context = get_context();
292
293 context.perform_render_passes();
295
296 context.gl.render_pass(None);
297 context.gl.viewport(None);
298 context.gl.depth_test(false);
299 context.camera_matrix = None;
300}
301
302pub(crate) struct CameraState {
303 render_pass: Option<miniquad::RenderPass>,
304 depth_test: bool,
305 matrix: Option<Mat4>,
306}
307
308pub fn push_camera_state() {
309 let context = get_context();
310
311 let camera_state = CameraState {
312 render_pass: context.gl.get_active_render_pass(),
313 depth_test: context.gl.is_depth_test_enabled(),
314 matrix: context.camera_matrix,
315 };
316 context.camera_stack.push(camera_state);
317}
318
319pub fn pop_camera_state() {
320 let context = get_context();
321
322 if let Some(camera_state) = context.camera_stack.pop() {
323 context.perform_render_passes();
324
325 context.gl.render_pass(camera_state.render_pass);
326 context.gl.depth_test(camera_state.depth_test);
327 context.camera_matrix = camera_state.matrix;
328 }
329}