1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::World;
3use crate::engine::ecs::component::Camera3DComponent;
4use crate::engine::ecs::component::CameraXRComponent;
5use crate::engine::ecs::system::System;
6use crate::engine::ecs::system::TransformSystem;
7use crate::engine::graphics::VisualWorld;
8use crate::engine::graphics::primitives::Transform;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct CameraHandle(pub u32);
12
13#[derive(Debug, Clone, Copy)]
14pub struct Camera3D {
15 pub view: [[f32; 4]; 4],
16 pub proj: [[f32; 4]; 4],
17 pub transform: Transform,
18}
19
20#[derive(Debug, Clone, Copy)]
21pub struct Camera2D {
22 pub view: [[f32; 4]; 4],
23 pub proj: [[f32; 4]; 4],
24 pub transform: Transform,
25}
26
27#[derive(Debug, Clone, Copy)]
28enum AnyCamera {
29 Camera3D(Camera3D),
30 Camera2D(Camera2D),
31}
32
33impl Camera3D {
34 pub fn identity() -> Self {
35 Self {
36 view: [
37 [1.0, 0.0, 0.0, 0.0],
38 [0.0, 1.0, 0.0, 0.0],
39 [0.0, 0.0, 1.0, 0.0],
40 [0.0, 0.0, 0.0, 1.0],
41 ],
42 proj: [
43 [1.0, 0.0, 0.0, 0.0],
44 [0.0, 1.0, 0.0, 0.0],
45 [0.0, 0.0, 1.0, 0.0],
46 [0.0, 0.0, 0.0, 1.0],
47 ],
48 transform: Transform::default(),
49 }
50 }
51
52 pub fn perspective_rh_zo(
58 fov_y_radians: f32,
59 aspect: f32,
60 z_near: f32,
61 z_far: f32,
62 ) -> [[f32; 4]; 4] {
63 let f = 1.0 / (0.5 * fov_y_radians).tan();
66 let nf = 1.0 / (z_near - z_far);
67
68 [
74 [f / aspect, 0.0, 0.0, 0.0],
75 [0.0, f, 0.0, 0.0],
76 [0.0, 0.0, z_far * nf, -1.0],
77 [0.0, 0.0, (z_near * z_far) * nf, 0.0],
78 ]
79 }
80}
81
82impl Camera2D {
83 pub fn identity() -> Self {
84 Self {
85 view: [
86 [1.0, 0.0, 0.0, 0.0],
87 [0.0, 1.0, 0.0, 0.0],
88 [0.0, 0.0, 1.0, 0.0],
89 [0.0, 0.0, 0.0, 1.0],
90 ],
91 proj: [
92 [1.0, 0.0, 0.0, 0.0],
93 [0.0, 1.0, 0.0, 0.0],
94 [0.0, 0.0, 1.0, 0.0],
95 [0.0, 0.0, 0.0, 1.0],
96 ],
97 transform: Transform::default(),
98 }
99 }
100}
101
102#[derive(Debug, Default)]
103pub struct CameraSystem {
104 next_handle: u32,
105 cameras: Vec<(CameraHandle, AnyCamera)>,
106 camera2d_components: std::collections::HashMap<CameraHandle, ComponentId>,
107 camera3d_components: std::collections::HashMap<CameraHandle, ComponentId>,
108 pub active_window_camera: Option<CameraHandle>,
109 pub active_xr_camera: Option<ComponentId>,
110
111 last_viewport: Option<[f32; 2]>,
113}
114
115impl CameraSystem {
116 pub fn new() -> Self {
117 Self::default()
118 }
119
120 pub fn register_camera(
124 &mut self,
125 world: &mut World,
126 visuals: &mut VisualWorld,
127 component: ComponentId,
128 ) -> CameraHandle {
129 let (w, h) = {
131 let vp = visuals.viewport();
132 (vp[0], vp[1])
133 };
134 let aspect = if h > 0.0 { w / h } else { 1.0 };
135
136 let (fov_y_deg, z_near, z_far) = world
137 .get_component_by_id_as::<Camera3DComponent>(component)
138 .map(|c| (c.fov_y_degrees, c.z_near, c.z_far))
139 .unwrap_or((
140 Camera3DComponent::DEFAULT_FOV_Y_DEGREES,
141 Camera3DComponent::DEFAULT_Z_NEAR,
142 Camera3DComponent::DEFAULT_Z_FAR,
143 ));
144 let proj = Camera3D::perspective_rh_zo(fov_y_deg.to_radians(), aspect, z_near, z_far);
145
146 let (view, transform) = if let Some(model) = TransformSystem::world_model(world, component)
149 {
150 (
151 invert_affine_transform(&model),
152 transform_from_matrix_world(model),
153 )
154 } else {
155 let ident = Camera3D::identity();
156 (ident.view, ident.transform)
157 };
158
159 let cam = Camera3D {
160 view,
161 proj,
162 transform,
163 };
164
165 let h = CameraHandle(self.next_handle);
166 self.next_handle = self.next_handle.wrapping_add(1);
167
168 self.cameras.push((h, AnyCamera::Camera3D(cam)));
169 self.camera3d_components.insert(h, component);
170
171 self.active_window_camera = Some(h);
173 visuals.set_camera_mono_for_target_with_transform(
174 crate::engine::graphics::CameraTarget::Window,
175 cam.view,
176 cam.proj,
177 cam.transform,
178 );
179
180 h
181 }
182
183 pub fn set_active_window_camera(&mut self, visuals: &mut VisualWorld, h: CameraHandle) {
184 if self.active_window_camera == Some(h) {
185 return;
186 }
187
188 if let Some((_, cam)) = self.cameras.iter().find(|(ch, _)| *ch == h) {
189 self.active_window_camera = Some(h);
190 match *cam {
191 AnyCamera::Camera3D(cam3d) => {
192 visuals.set_camera_mono_for_target_with_transform(
193 crate::engine::graphics::CameraTarget::Window,
194 cam3d.view,
195 cam3d.proj,
196 cam3d.transform,
197 );
198 }
199 AnyCamera::Camera2D(cam2d) => {
200 visuals.set_camera_mono_for_target_with_transform(
201 crate::engine::graphics::CameraTarget::Window,
202 cam2d.view,
203 cam2d.proj,
204 cam2d.transform,
205 );
206 }
207 }
208 }
209 }
210
211 fn window_camera_component_enabled(&self, world: &World, handle: CameraHandle) -> bool {
212 if let Some(cid) = self.camera3d_components.get(&handle) {
213 return world
214 .get_component_by_id_as::<Camera3DComponent>(*cid)
215 .is_some_and(|c| {
216 c.enabled && matches!(c.target, crate::engine::graphics::CameraTarget::Window)
217 });
218 }
219 if let Some(cid) = self.camera2d_components.get(&handle) {
220 return world
221 .get_component_by_id_as::<crate::engine::ecs::component::Camera2DComponent>(*cid)
222 .is_some_and(|c| {
223 matches!(c.target, crate::engine::graphics::CameraTarget::Window)
224 });
225 }
226 false
227 }
228
229 fn update_active_window_camera(&mut self, world: &World, visuals: &mut VisualWorld) {
230 if let Some(active) = self.active_window_camera {
231 if self.window_camera_component_enabled(world, active) {
232 return;
233 }
234 }
235
236 let next = self
237 .cameras
238 .iter()
239 .map(|(handle, _)| *handle)
240 .rev()
241 .find(|&handle| self.window_camera_component_enabled(world, handle));
242
243 self.active_window_camera = next;
244
245 if let Some(handle) = next {
246 if let Some((_, cam)) = self.cameras.iter().find(|(ch, _)| *ch == handle) {
247 match *cam {
248 AnyCamera::Camera3D(cam3d) => visuals
249 .set_camera_mono_for_target_with_transform(
250 crate::engine::graphics::CameraTarget::Window,
251 cam3d.view,
252 cam3d.proj,
253 cam3d.transform,
254 ),
255 AnyCamera::Camera2D(cam2d) => visuals
256 .set_camera_mono_for_target_with_transform(
257 crate::engine::graphics::CameraTarget::Window,
258 cam2d.view,
259 cam2d.proj,
260 cam2d.transform,
261 ),
262 }
263 }
264 }
265 }
266
267 pub fn set_active_xr_camera(
268 &mut self,
269 world: &World,
270 visuals: &mut VisualWorld,
271 component: ComponentId,
272 ) {
273 if world
275 .get_component_by_id_as::<CameraXRComponent>(component)
276 .is_some_and(|c| c.enabled)
277 {
278 self.active_xr_camera = Some(component);
279 visuals.set_active_xr_camera(Some(component));
280 }
281 }
282
283 pub fn update_camera_2d_from_parent_transform(
287 &mut self,
288 world: &World,
289 visuals: &mut VisualWorld,
290 camera2d_component_id: ComponentId,
291 transform_component_id: ComponentId,
292 ) {
293 let Some(camera2d_comp) = world
294 .get_component_by_id_as::<crate::engine::ecs::component::Camera2DComponent>(
295 camera2d_component_id,
296 )
297 else {
298 return;
299 };
300
301 if let Some(handle) = camera2d_comp.handle {
302 if self.active_window_camera == Some(handle) {
303 if world
305 .get_component_by_id_as::<crate::engine::ecs::component::TransformComponent>(
306 transform_component_id,
307 )
308 .is_none()
309 {
310 return;
311 }
312
313 let model = TransformSystem::world_model(world, camera2d_component_id)
315 .unwrap_or_else(|| Camera3D::identity().view);
316
317 let view = invert_affine_transform(&model);
318
319 let vp = visuals.viewport();
321 let inv_aspect = if vp[0] > 0.0 { vp[1] / vp[0] } else { 1.0 };
322 let proj = [
323 [inv_aspect, 0.0, 0.0, 0.0],
324 [0.0, 1.0, 0.0, 0.0],
325 [0.0, 0.0, 1.0, 0.0],
326 [0.0, 0.0, 0.0, 1.0],
327 ];
328
329 if let Some((_, AnyCamera::Camera2D(cam2d))) =
331 self.cameras.iter_mut().find(|(ch, _)| *ch == handle)
332 {
333 cam2d.view = view;
334 cam2d.proj = proj;
335 cam2d.transform = transform_from_matrix_world(model);
336 }
337
338 visuals.set_camera_mono_for_target_with_transform(
339 crate::engine::graphics::CameraTarget::Window,
340 view,
341 proj,
342 transform_from_matrix_world(model),
343 );
344 }
345 }
346 }
347
348 pub fn register_camera2d(
350 &mut self,
351 _world: &mut World,
352 _visuals: &mut VisualWorld,
353 component: ComponentId,
354 ) -> CameraHandle {
355 let h = CameraHandle(self.next_handle);
356 self.next_handle = self.next_handle.wrapping_add(1);
357
358 self.cameras
359 .push((h, AnyCamera::Camera2D(Camera2D::identity())));
360 self.camera2d_components.insert(h, component);
361
362 self.active_window_camera = Some(h);
364
365 h
366 }
367
368 pub fn active_window_camera_matrices(&self) -> Option<([[f32; 4]; 4], [[f32; 4]; 4])> {
369 let h = self.active_window_camera?;
370 let (_, cam) = self.cameras.iter().find(|(ch, _)| *ch == h)?;
371 match *cam {
372 AnyCamera::Camera3D(cam3d) => Some((cam3d.view, cam3d.proj)),
373 AnyCamera::Camera2D(cam2d) => Some((cam2d.view, cam2d.proj)),
374 }
375 }
376
377 pub fn has_active_window_camera(&self) -> bool {
378 self.active_window_camera_matrices().is_some()
379 }
380
381 pub fn update_camera_3d_from_parent_transform(
385 &mut self,
386 world: &World,
387 visuals: &mut VisualWorld,
388 camera3d_component_id: ComponentId,
389 transform_component_id: ComponentId,
390 ) {
391 let Some(camera3d_comp) = world
392 .get_component_by_id_as::<crate::engine::ecs::component::Camera3DComponent>(
393 camera3d_component_id,
394 )
395 else {
396 return;
397 };
398
399 if let Some(handle) = camera3d_comp.handle {
400 if self.active_window_camera == Some(handle) {
401 if world
403 .get_component_by_id_as::<crate::engine::ecs::component::TransformComponent>(
404 transform_component_id,
405 )
406 .is_none()
407 {
408 return;
409 }
410
411 let model = TransformSystem::world_model(world, camera3d_component_id)
413 .unwrap_or_else(|| Camera3D::identity().view);
414 let view = invert_affine_transform(&model);
415
416 let vp = visuals.viewport();
417 let aspect = if vp[1] > 0.0 { vp[0] / vp[1] } else { 1.0 };
418 let proj = Camera3D::perspective_rh_zo(
419 camera3d_comp.fov_y_degrees.to_radians(),
420 aspect,
421 camera3d_comp.z_near,
422 camera3d_comp.z_far,
423 );
424
425 if let Some((_, AnyCamera::Camera3D(cam3d))) =
427 self.cameras.iter_mut().find(|(ch, _)| *ch == handle)
428 {
429 cam3d.view = view;
430 cam3d.proj = proj;
431 cam3d.transform = transform_from_matrix_world(model);
432 }
433
434 visuals.set_camera_mono_for_target_with_transform(
435 crate::engine::graphics::CameraTarget::Window,
436 view,
437 proj,
438 transform_from_matrix_world(model),
439 );
440 }
441 }
442 }
443
444 fn update_active_xr_camera(&mut self, world: &World, visuals: &mut VisualWorld) {
445 if let Some(active) = self.active_xr_camera {
447 if let Some(c) = world.get_component_by_id_as::<CameraXRComponent>(active) {
448 if c.enabled {
449 visuals.set_active_xr_camera(Some(active));
450 return;
451 }
452 }
453 }
454
455 let next = world.all_components().find(|&id| {
457 world
458 .get_component_by_id_as::<CameraXRComponent>(id)
459 .is_some_and(|c| {
460 c.enabled && matches!(c.target, crate::engine::graphics::CameraTarget::Xr)
461 })
462 });
463
464 self.active_xr_camera = next;
465 visuals.set_active_xr_camera(next);
466 }
467}
468
469fn invert_affine_transform(m: &[[f32; 4]; 4]) -> [[f32; 4]; 4] {
474 let c0 = [m[0][0], m[0][1], m[0][2]];
476 let c1 = [m[1][0], m[1][1], m[1][2]];
477 let c2 = [m[2][0], m[2][1], m[2][2]];
478
479 let a00 = c0[0];
481 let a10 = c0[1];
482 let a20 = c0[2];
483 let a01 = c1[0];
484 let a11 = c1[1];
485 let a21 = c1[2];
486 let a02 = c2[0];
487 let a12 = c2[1];
488 let a22 = c2[2];
489
490 let det = a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20)
491 + a02 * (a10 * a21 - a11 * a20);
492
493 if det.abs() < 1e-8 {
494 return Camera3D::identity().view;
495 }
496 let inv_det = 1.0 / det;
497
498 let inv00 = (a11 * a22 - a12 * a21) * inv_det;
500 let inv01 = (a02 * a21 - a01 * a22) * inv_det;
501 let inv02 = (a01 * a12 - a02 * a11) * inv_det;
502
503 let inv10 = (a12 * a20 - a10 * a22) * inv_det;
504 let inv11 = (a00 * a22 - a02 * a20) * inv_det;
505 let inv12 = (a02 * a10 - a00 * a12) * inv_det;
506
507 let inv20 = (a10 * a21 - a11 * a20) * inv_det;
508 let inv21 = (a01 * a20 - a00 * a21) * inv_det;
509 let inv22 = (a00 * a11 - a01 * a10) * inv_det;
510
511 let tx = m[3][0];
513 let ty = m[3][1];
514 let tz = m[3][2];
515
516 let itx = -(inv00 * tx + inv01 * ty + inv02 * tz);
517 let ity = -(inv10 * tx + inv11 * ty + inv12 * tz);
518 let itz = -(inv20 * tx + inv21 * ty + inv22 * tz);
519
520 [
521 [inv00, inv10, inv20, 0.0],
522 [inv01, inv11, inv21, 0.0],
523 [inv02, inv12, inv22, 0.0],
524 [itx, ity, itz, 1.0],
525 ]
526}
527
528impl System for CameraSystem {
529 fn tick(
530 &mut self,
531 world: &mut World,
532 visuals: &mut VisualWorld,
533 _input: &crate::engine::user_input::InputState,
534 _dt_sec: f32,
535 ) {
536 self.update_active_window_camera(world, visuals);
537 self.update_active_xr_camera(world, visuals);
539
540 let vp = visuals.viewport();
541 let prev_vp = self.last_viewport;
542 self.last_viewport = Some(vp);
543
544 let viewport_changed = prev_vp != Some(vp);
547
548 if !viewport_changed {
549 return;
550 }
551
552 let Some(active_handle) = self.active_window_camera else {
553 let inv_aspect = if vp[0] > 0.0 { vp[1] / vp[0] } else { 1.0 };
556 let view = Camera3D::identity().view;
557 let proj = [
558 [inv_aspect, 0.0, 0.0, 0.0],
559 [0.0, 1.0, 0.0, 0.0],
560 [0.0, 0.0, 1.0, 0.0],
561 [0.0, 0.0, 0.0, 1.0],
562 ];
563 visuals.set_camera_mono_for_target_with_transform(
564 crate::engine::graphics::CameraTarget::Window,
565 view,
566 proj,
567 Transform::default(),
568 );
569 return;
570 };
571
572 let Some((_, cam)) = self.cameras.iter_mut().find(|(ch, _)| *ch == active_handle) else {
574 return;
575 };
576
577 match cam {
578 AnyCamera::Camera2D(cam2d) => {
579 let inv_aspect = if vp[0] > 0.0 { vp[1] / vp[0] } else { 1.0 };
580 cam2d.proj = [
581 [inv_aspect, 0.0, 0.0, 0.0],
582 [0.0, 1.0, 0.0, 0.0],
583 [0.0, 0.0, 1.0, 0.0],
584 [0.0, 0.0, 0.0, 1.0],
585 ];
586 visuals.set_camera_mono_for_target_with_transform(
587 crate::engine::graphics::CameraTarget::Window,
588 cam2d.view,
589 cam2d.proj,
590 cam2d.transform,
591 );
592 }
593 AnyCamera::Camera3D(cam3d) => {
594 let aspect = if vp[1] > 0.0 { vp[0] / vp[1] } else { 1.0 };
595
596 let (fov_y_deg, z_near, z_far) = self
597 .camera3d_components
598 .get(&active_handle)
599 .and_then(|cid| world.get_component_by_id_as::<Camera3DComponent>(*cid))
600 .map(|c| (c.fov_y_degrees, c.z_near, c.z_far))
601 .unwrap_or((
602 Camera3DComponent::DEFAULT_FOV_Y_DEGREES,
603 Camera3DComponent::DEFAULT_Z_NEAR,
604 Camera3DComponent::DEFAULT_Z_FAR,
605 ));
606
607 cam3d.proj =
608 Camera3D::perspective_rh_zo(fov_y_deg.to_radians(), aspect, z_near, z_far);
609 visuals.set_camera_mono_for_target_with_transform(
610 crate::engine::graphics::CameraTarget::Window,
611 cam3d.view,
612 cam3d.proj,
613 cam3d.transform,
614 );
615 }
616 }
617 }
618}
619
620fn transform_from_matrix_world(m: [[f32; 4]; 4]) -> Transform {
621 let mut t = Transform::default();
622 t.model = m;
623 t.matrix_world = m;
624 t.translation = [m[3][0], m[3][1], m[3][2]];
625 t
626}