1pub struct Camera2D {
3 pub x: f32,
4 pub y: f32,
5 pub zoom: f32,
6 pub viewport_size: [f32; 2],
7}
8
9impl Default for Camera2D {
10 fn default() -> Self {
11 Self {
12 x: 0.0,
13 y: 0.0,
14 zoom: 1.0,
15 viewport_size: [800.0, 600.0],
16 }
17 }
18}
19
20impl Camera2D {
21 pub fn view_proj(&self) -> [f32; 16] {
28 let half_w = self.viewport_size[0] / (2.0 * self.zoom);
29 let half_h = self.viewport_size[1] / (2.0 * self.zoom);
30
31 let left = self.x - half_w;
32 let right = self.x + half_w;
33 let top = self.y - half_h;
34 let bottom = self.y + half_h;
35
36 let sx = 2.0 / (right - left);
38 let sy = 2.0 / (top - bottom); let tx = -(right + left) / (right - left);
40 let ty = -(top + bottom) / (top - bottom);
41
42 [
43 sx, 0.0, 0.0, 0.0,
44 0.0, sy, 0.0, 0.0,
45 0.0, 0.0, 1.0, 0.0,
46 tx, ty, 0.0, 1.0,
47 ]
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn default_camera_has_expected_values() {
57 let cam = Camera2D::default();
58 assert_eq!(cam.x, 0.0);
59 assert_eq!(cam.y, 0.0);
60 assert_eq!(cam.zoom, 1.0);
61 assert_eq!(cam.viewport_size, [800.0, 600.0]);
62 }
63
64 #[test]
65 fn view_proj_at_origin_with_zoom_1() {
66 let cam = Camera2D {
67 x: 0.0,
68 y: 0.0,
69 zoom: 1.0,
70 viewport_size: [800.0, 600.0],
71 };
72 let mat = cam.view_proj();
73
74 let expected_sx = 2.0 / 800.0; let expected_sy = 2.0 / -600.0; assert!((mat[0] - expected_sx).abs() < 1e-6, "sx mismatch");
81 assert!((mat[5] - expected_sy).abs() < 1e-6, "sy mismatch");
82 assert_eq!(mat[12], 0.0, "tx should be 0 at origin");
83 assert_eq!(mat[13], 0.0, "ty should be 0 at origin");
84 }
85
86 #[test]
87 fn view_proj_with_camera_offset() {
88 let cam = Camera2D {
89 x: 100.0,
90 y: 50.0,
91 zoom: 1.0,
92 viewport_size: [800.0, 600.0],
93 };
94 let mat = cam.view_proj();
95
96 assert!((mat[12] - -0.25).abs() < 1e-6, "tx mismatch for offset camera");
101
102 assert!((mat[13] - (100.0 / 600.0)).abs() < 1e-5, "ty mismatch for offset camera");
105 }
106
107 #[test]
108 fn view_proj_with_zoom() {
109 let cam = Camera2D {
110 x: 0.0,
111 y: 0.0,
112 zoom: 2.0,
113 viewport_size: [800.0, 600.0],
114 };
115 let mat = cam.view_proj();
116
117 let expected_sx = 2.0 / 400.0; let expected_sy = 2.0 / -300.0; assert!((mat[0] - expected_sx).abs() < 1e-6, "sx mismatch with zoom");
125 assert!((mat[5] - expected_sy).abs() < 1e-6, "sy mismatch with zoom");
126 }
127
128 #[test]
129 fn view_proj_with_different_viewport() {
130 let cam = Camera2D {
131 x: 0.0,
132 y: 0.0,
133 zoom: 1.0,
134 viewport_size: [1920.0, 1080.0],
135 };
136 let mat = cam.view_proj();
137
138 let expected_sx = 2.0 / 1920.0;
139 let expected_sy = 2.0 / -1080.0;
140
141 assert!((mat[0] - expected_sx).abs() < 1e-6, "sx mismatch for HD viewport");
142 assert!((mat[5] - expected_sy).abs() < 1e-6, "sy mismatch for HD viewport");
143 }
144
145 #[test]
146 fn view_proj_matrix_is_column_major() {
147 let cam = Camera2D::default();
148 let mat = cam.view_proj();
149
150 assert_eq!(mat[10], 1.0, "z scale should be 1.0");
158 assert_eq!(mat[15], 1.0, "w component should be 1.0");
159 assert_eq!(mat[2], 0.0, "unused z component");
160 assert_eq!(mat[3], 0.0, "unused w component");
161 }
162
163 #[test]
164 fn very_high_zoom_produces_small_view_area() {
165 let cam = Camera2D {
166 x: 0.0,
167 y: 0.0,
168 zoom: 10.0,
169 viewport_size: [800.0, 600.0],
170 };
171 let mat = cam.view_proj();
172
173 let expected_sx = 2.0 / 80.0; let expected_sy = 2.0 / -60.0; assert!((mat[0] - expected_sx).abs() < 1e-6);
180 assert!((mat[5] - expected_sy).abs() < 1e-5);
181 }
182
183 #[test]
184 fn very_low_zoom_produces_large_view_area() {
185 let cam = Camera2D {
186 x: 0.0,
187 y: 0.0,
188 zoom: 0.1,
189 viewport_size: [800.0, 600.0],
190 };
191 let mat = cam.view_proj();
192
193 let expected_sx = 2.0 / 8000.0; let expected_sy = 2.0 / -6000.0; assert!((mat[0] - expected_sx).abs() < 1e-7);
200 assert!((mat[5] - expected_sy).abs() < 1e-6);
201 }
202
203 #[test]
204 fn negative_camera_position() {
205 let cam = Camera2D {
206 x: -100.0,
207 y: -50.0,
208 zoom: 1.0,
209 viewport_size: [800.0, 600.0],
210 };
211 let mat = cam.view_proj();
212
213 assert!((mat[12] - 0.25).abs() < 1e-6);
217 }
218
219 #[test]
220 fn square_viewport() {
221 let cam = Camera2D {
222 x: 0.0,
223 y: 0.0,
224 zoom: 1.0,
225 viewport_size: [600.0, 600.0],
226 };
227 let mat = cam.view_proj();
228
229 let expected_sx = 2.0 / 600.0;
230 let expected_sy = 2.0 / -600.0;
231
232 assert!((mat[0] - expected_sx).abs() < 1e-6);
233 assert!((mat[5] - expected_sy).abs() < 1e-6);
234 }
235}