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 {
125 let mat = self.matrix();
126 let transform = mat.transform_point3(vec3(point.x, point.y, 0.));
127
128 vec2(
129 (transform.x / 2. + 0.5) * screen_width(),
130 (0.5 - transform.y / 2.) * screen_height(),
131 )
132 }
133
134 pub fn screen_to_world(&self, point: Vec2) -> Vec2 {
138 let dims = self
139 .viewport()
140 .map(|(vx, vy, vw, vh)| Rect {
141 x: vx as f32,
142 y: screen_height() - (vy + vh) as f32,
143 w: vw as f32,
144 h: vh as f32,
145 })
146 .unwrap_or(Rect {
147 x: 0.0,
148 y: 0.0,
149 w: screen_width(),
150 h: screen_height(),
151 });
152
153 let point = vec2(
154 (point.x - dims.x) / dims.w * 2. - 1.,
155 1. - (point.y - dims.y) / dims.h * 2.,
156 );
157 let inv_mat = self.matrix().inverse();
158 let transform = inv_mat.transform_point3(vec3(point.x, point.y, 0.));
159
160 vec2(transform.x, transform.y)
161 }
162}
163
164#[derive(Debug, Clone, Copy)]
165pub enum Projection {
166 Perspective,
167 Orthographics,
168}
169
170#[derive(Debug)]
171pub struct Camera3D {
172 pub position: Vec3,
174 pub target: Vec3,
176 pub up: Vec3,
178 pub fovy: f32,
181 pub aspect: Option<f32>,
185 pub projection: Projection,
187
188 pub render_target: Option<RenderTarget>,
192
193 pub viewport: Option<(i32, i32, i32, i32)>,
201
202 pub z_near: f32,
204 pub z_far: f32,
206}
207
208impl Default for Camera3D {
209 fn default() -> Camera3D {
210 Camera3D {
211 position: vec3(0., -10., 0.),
212 target: vec3(0., 0., 0.),
213 aspect: None,
214 up: vec3(0., 0., 1.),
215 fovy: 45.0_f32.to_radians(),
216 projection: Projection::Perspective,
217 render_target: None,
218 viewport: None,
219 z_near: 0.01,
220 z_far: 10000.0,
221 }
222 }
223}
224
225impl Camera for Camera3D {
226 fn matrix(&self) -> Mat4 {
227 let aspect = self.aspect.unwrap_or(screen_width() / screen_height());
228
229 match self.projection {
230 Projection::Perspective => {
231 Mat4::perspective_rh_gl(self.fovy, aspect, self.z_near, self.z_far)
232 * Mat4::look_at_rh(self.position, self.target, self.up)
233 }
234 Projection::Orthographics => {
235 let top = self.fovy / 2.0;
236 let right = top * aspect;
237
238 Mat4::orthographic_rh_gl(-right, right, -top, top, self.z_near, self.z_far)
239 * Mat4::look_at_rh(self.position, self.target, self.up)
240 }
241 }
242 }
243
244 fn depth_enabled(&self) -> bool {
245 true
246 }
247
248 fn render_pass(&self) -> Option<RenderPass> {
249 self.render_target.as_ref().map(|rt| rt.render_pass.clone())
250 }
251
252 fn viewport(&self) -> Option<(i32, i32, i32, i32)> {
253 self.viewport
254 }
255}
256
257pub fn set_camera(camera: &dyn Camera) {
259 let context = get_context();
260
261 context.perform_render_passes();
263
264 context
265 .gl
266 .render_pass(camera.render_pass().map(|rt| rt.raw_miniquad_id()));
267
268 context.gl.viewport(camera.viewport());
269 context.gl.depth_test(camera.depth_enabled());
270 context.camera_matrix = Some(camera.matrix());
271}
272
273pub fn set_default_camera() {
275 let context = get_context();
276
277 context.perform_render_passes();
279
280 context.gl.render_pass(None);
281 context.gl.viewport(None);
282 context.gl.depth_test(false);
283 context.camera_matrix = None;
284}
285
286pub(crate) struct CameraState {
287 render_pass: Option<miniquad::RenderPass>,
288 depth_test: bool,
289 matrix: Option<Mat4>,
290}
291
292pub fn push_camera_state() {
293 let context = get_context();
294
295 let camera_state = CameraState {
296 render_pass: context.gl.get_active_render_pass(),
297 depth_test: context.gl.is_depth_test_enabled(),
298 matrix: context.camera_matrix,
299 };
300 context.camera_stack.push(camera_state);
301}
302
303pub fn pop_camera_state() {
304 let context = get_context();
305
306 if let Some(camera_state) = context.camera_stack.pop() {
307 context.perform_render_passes();
308
309 context.gl.render_pass(camera_state.render_pass);
310 context.gl.depth_test(camera_state.depth_test);
311 context.camera_matrix = camera_state.matrix;
312 }
313}