use crate::engine::ecs::ComponentId;
use crate::engine::ecs::World;
use crate::engine::ecs::component::Camera3DComponent;
use crate::engine::ecs::component::CameraXRComponent;
use crate::engine::ecs::system::System;
use crate::engine::ecs::system::TransformSystem;
use crate::engine::graphics::VisualWorld;
use crate::engine::graphics::primitives::Transform;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CameraHandle(pub u32);
#[derive(Debug, Clone, Copy)]
pub struct Camera3D {
pub view: [[f32; 4]; 4],
pub proj: [[f32; 4]; 4],
pub transform: Transform,
}
#[derive(Debug, Clone, Copy)]
pub struct Camera2D {
pub view: [[f32; 4]; 4],
pub proj: [[f32; 4]; 4],
pub transform: Transform,
}
#[derive(Debug, Clone, Copy)]
enum AnyCamera {
Camera3D(Camera3D),
Camera2D(Camera2D),
}
impl Camera3D {
pub fn identity() -> Self {
Self {
view: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
proj: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
transform: Transform::default(),
}
}
pub fn perspective_rh_zo(
fov_y_radians: f32,
aspect: f32,
z_near: f32,
z_far: f32,
) -> [[f32; 4]; 4] {
let f = 1.0 / (0.5 * fov_y_radians).tan();
let nf = 1.0 / (z_near - z_far);
[
[f / aspect, 0.0, 0.0, 0.0],
[0.0, f, 0.0, 0.0],
[0.0, 0.0, z_far * nf, -1.0],
[0.0, 0.0, (z_near * z_far) * nf, 0.0],
]
}
}
impl Camera2D {
pub fn identity() -> Self {
Self {
view: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
proj: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
transform: Transform::default(),
}
}
}
#[derive(Debug, Default)]
pub struct CameraSystem {
next_handle: u32,
cameras: Vec<(CameraHandle, AnyCamera)>,
camera2d_components: std::collections::HashMap<CameraHandle, ComponentId>,
camera3d_components: std::collections::HashMap<CameraHandle, ComponentId>,
active_window_camera: Option<CameraHandle>,
active_xr_camera: Option<ComponentId>,
last_viewport: Option<[f32; 2]>,
}
impl CameraSystem {
pub fn new() -> Self {
Self::default()
}
pub fn register_camera(
&mut self,
world: &mut World,
visuals: &mut VisualWorld,
component: ComponentId,
) -> CameraHandle {
let (w, h) = {
let vp = visuals.viewport();
(vp[0], vp[1])
};
let aspect = if h > 0.0 { w / h } else { 1.0 };
let (fov_y_deg, z_near, z_far) = world
.get_component_by_id_as::<Camera3DComponent>(component)
.map(|c| (c.fov_y_degrees, c.z_near, c.z_far))
.unwrap_or((
Camera3DComponent::DEFAULT_FOV_Y_DEGREES,
Camera3DComponent::DEFAULT_Z_NEAR,
Camera3DComponent::DEFAULT_Z_FAR,
));
let proj = Camera3D::perspective_rh_zo(fov_y_deg.to_radians(), aspect, z_near, z_far);
let (view, transform) = if let Some(model) = TransformSystem::world_model(world, component)
{
(
invert_affine_transform(&model),
transform_from_matrix_world(model),
)
} else {
let ident = Camera3D::identity();
(ident.view, ident.transform)
};
let cam = Camera3D {
view,
proj,
transform,
};
let h = CameraHandle(self.next_handle);
self.next_handle = self.next_handle.wrapping_add(1);
self.cameras.push((h, AnyCamera::Camera3D(cam)));
self.camera3d_components.insert(h, component);
self.active_window_camera = Some(h);
visuals.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
cam.view,
cam.proj,
cam.transform,
);
h
}
pub fn set_active_window_camera(&mut self, visuals: &mut VisualWorld, h: CameraHandle) {
if self.active_window_camera == Some(h) {
return;
}
if let Some((_, cam)) = self.cameras.iter().find(|(ch, _)| *ch == h) {
self.active_window_camera = Some(h);
match *cam {
AnyCamera::Camera3D(cam3d) => {
visuals.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
cam3d.view,
cam3d.proj,
cam3d.transform,
);
}
AnyCamera::Camera2D(cam2d) => {
visuals.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
cam2d.view,
cam2d.proj,
cam2d.transform,
);
}
}
}
}
fn window_camera_component_enabled(&self, world: &World, handle: CameraHandle) -> bool {
if let Some(cid) = self.camera3d_components.get(&handle) {
return world
.get_component_by_id_as::<Camera3DComponent>(*cid)
.is_some_and(|c| {
c.enabled && matches!(c.target, crate::engine::graphics::CameraTarget::Window)
});
}
if let Some(cid) = self.camera2d_components.get(&handle) {
return world
.get_component_by_id_as::<crate::engine::ecs::component::Camera2DComponent>(*cid)
.is_some_and(|c| {
matches!(c.target, crate::engine::graphics::CameraTarget::Window)
});
}
false
}
fn update_active_window_camera(&mut self, world: &World, visuals: &mut VisualWorld) {
if let Some(active) = self.active_window_camera {
if self.window_camera_component_enabled(world, active) {
return;
}
}
let next = self
.cameras
.iter()
.map(|(handle, _)| *handle)
.rev()
.find(|&handle| self.window_camera_component_enabled(world, handle));
self.active_window_camera = next;
if let Some(handle) = next {
if let Some((_, cam)) = self.cameras.iter().find(|(ch, _)| *ch == handle) {
match *cam {
AnyCamera::Camera3D(cam3d) => visuals
.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
cam3d.view,
cam3d.proj,
cam3d.transform,
),
AnyCamera::Camera2D(cam2d) => visuals
.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
cam2d.view,
cam2d.proj,
cam2d.transform,
),
}
}
}
}
pub fn set_active_xr_camera(
&mut self,
world: &World,
visuals: &mut VisualWorld,
component: ComponentId,
) {
if world
.get_component_by_id_as::<CameraXRComponent>(component)
.is_some_and(|c| c.enabled)
{
self.active_xr_camera = Some(component);
visuals.set_active_xr_camera(Some(component));
}
}
pub fn update_camera_2d_from_parent_transform(
&mut self,
world: &World,
visuals: &mut VisualWorld,
camera2d_component_id: ComponentId,
transform_component_id: ComponentId,
) {
let Some(camera2d_comp) = world
.get_component_by_id_as::<crate::engine::ecs::component::Camera2DComponent>(
camera2d_component_id,
)
else {
return;
};
if let Some(handle) = camera2d_comp.handle {
if self.active_window_camera == Some(handle) {
if world
.get_component_by_id_as::<crate::engine::ecs::component::TransformComponent>(
transform_component_id,
)
.is_none()
{
return;
}
let model = TransformSystem::world_model(world, camera2d_component_id)
.unwrap_or_else(|| Camera3D::identity().view);
let view = invert_affine_transform(&model);
let vp = visuals.viewport();
let inv_aspect = if vp[0] > 0.0 { vp[1] / vp[0] } else { 1.0 };
let proj = [
[inv_aspect, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
if let Some((_, AnyCamera::Camera2D(cam2d))) =
self.cameras.iter_mut().find(|(ch, _)| *ch == handle)
{
cam2d.view = view;
cam2d.proj = proj;
cam2d.transform = transform_from_matrix_world(model);
}
visuals.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
view,
proj,
transform_from_matrix_world(model),
);
}
}
}
pub fn register_camera2d(
&mut self,
_world: &mut World,
_visuals: &mut VisualWorld,
component: ComponentId,
) -> CameraHandle {
let h = CameraHandle(self.next_handle);
self.next_handle = self.next_handle.wrapping_add(1);
self.cameras
.push((h, AnyCamera::Camera2D(Camera2D::identity())));
self.camera2d_components.insert(h, component);
self.active_window_camera = Some(h);
h
}
pub fn active_window_camera_matrices(&self) -> Option<([[f32; 4]; 4], [[f32; 4]; 4])> {
let h = self.active_window_camera?;
let (_, cam) = self.cameras.iter().find(|(ch, _)| *ch == h)?;
match *cam {
AnyCamera::Camera3D(cam3d) => Some((cam3d.view, cam3d.proj)),
AnyCamera::Camera2D(cam2d) => Some((cam2d.view, cam2d.proj)),
}
}
pub fn has_active_window_camera(&self) -> bool {
self.active_window_camera_matrices().is_some()
}
pub fn active_window_camera_component(&self) -> Option<ComponentId> {
self.active_window_camera
.and_then(|handle| self.camera3d_components.get(&handle).copied())
}
pub fn active_xr_camera_component(&self) -> Option<ComponentId> {
self.active_xr_camera
}
pub fn update_camera_3d_from_parent_transform(
&mut self,
world: &World,
visuals: &mut VisualWorld,
camera3d_component_id: ComponentId,
transform_component_id: ComponentId,
) {
let Some(camera3d_comp) = world
.get_component_by_id_as::<crate::engine::ecs::component::Camera3DComponent>(
camera3d_component_id,
)
else {
return;
};
if let Some(handle) = camera3d_comp.handle {
if self.active_window_camera == Some(handle) {
if world
.get_component_by_id_as::<crate::engine::ecs::component::TransformComponent>(
transform_component_id,
)
.is_none()
{
return;
}
let model = TransformSystem::world_model(world, camera3d_component_id)
.unwrap_or_else(|| Camera3D::identity().view);
let view = invert_affine_transform(&model);
let vp = visuals.viewport();
let aspect = if vp[1] > 0.0 { vp[0] / vp[1] } else { 1.0 };
let proj = Camera3D::perspective_rh_zo(
camera3d_comp.fov_y_degrees.to_radians(),
aspect,
camera3d_comp.z_near,
camera3d_comp.z_far,
);
if let Some((_, AnyCamera::Camera3D(cam3d))) =
self.cameras.iter_mut().find(|(ch, _)| *ch == handle)
{
cam3d.view = view;
cam3d.proj = proj;
cam3d.transform = transform_from_matrix_world(model);
}
visuals.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
view,
proj,
transform_from_matrix_world(model),
);
}
}
}
fn update_active_xr_camera(&mut self, world: &World, visuals: &mut VisualWorld) {
if let Some(active) = self.active_xr_camera {
if let Some(c) = world.get_component_by_id_as::<CameraXRComponent>(active) {
if c.enabled {
visuals.set_active_xr_camera(Some(active));
return;
}
}
}
let next = world.all_components().find(|&id| {
world
.get_component_by_id_as::<CameraXRComponent>(id)
.is_some_and(|c| {
c.enabled && matches!(c.target, crate::engine::graphics::CameraTarget::Xr)
})
});
self.active_xr_camera = next;
visuals.set_active_xr_camera(next);
}
}
fn invert_affine_transform(m: &[[f32; 4]; 4]) -> [[f32; 4]; 4] {
let c0 = [m[0][0], m[0][1], m[0][2]];
let c1 = [m[1][0], m[1][1], m[1][2]];
let c2 = [m[2][0], m[2][1], m[2][2]];
let a00 = c0[0];
let a10 = c0[1];
let a20 = c0[2];
let a01 = c1[0];
let a11 = c1[1];
let a21 = c1[2];
let a02 = c2[0];
let a12 = c2[1];
let a22 = c2[2];
let det = a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20)
+ a02 * (a10 * a21 - a11 * a20);
if det.abs() < 1e-8 {
return Camera3D::identity().view;
}
let inv_det = 1.0 / det;
let inv00 = (a11 * a22 - a12 * a21) * inv_det;
let inv01 = (a02 * a21 - a01 * a22) * inv_det;
let inv02 = (a01 * a12 - a02 * a11) * inv_det;
let inv10 = (a12 * a20 - a10 * a22) * inv_det;
let inv11 = (a00 * a22 - a02 * a20) * inv_det;
let inv12 = (a02 * a10 - a00 * a12) * inv_det;
let inv20 = (a10 * a21 - a11 * a20) * inv_det;
let inv21 = (a01 * a20 - a00 * a21) * inv_det;
let inv22 = (a00 * a11 - a01 * a10) * inv_det;
let tx = m[3][0];
let ty = m[3][1];
let tz = m[3][2];
let itx = -(inv00 * tx + inv01 * ty + inv02 * tz);
let ity = -(inv10 * tx + inv11 * ty + inv12 * tz);
let itz = -(inv20 * tx + inv21 * ty + inv22 * tz);
[
[inv00, inv10, inv20, 0.0],
[inv01, inv11, inv21, 0.0],
[inv02, inv12, inv22, 0.0],
[itx, ity, itz, 1.0],
]
}
impl System for CameraSystem {
fn tick(
&mut self,
world: &mut World,
visuals: &mut VisualWorld,
_input: &crate::engine::user_input::InputState,
_dt_sec: f32,
) {
self.update_active_window_camera(world, visuals);
self.update_active_xr_camera(world, visuals);
let vp = visuals.viewport();
let prev_vp = self.last_viewport;
self.last_viewport = Some(vp);
let viewport_changed = prev_vp != Some(vp);
if !viewport_changed {
return;
}
let Some(active_handle) = self.active_window_camera else {
let inv_aspect = if vp[0] > 0.0 { vp[1] / vp[0] } else { 1.0 };
let view = Camera3D::identity().view;
let proj = [
[inv_aspect, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
visuals.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
view,
proj,
Transform::default(),
);
return;
};
let Some((_, cam)) = self.cameras.iter_mut().find(|(ch, _)| *ch == active_handle) else {
return;
};
match cam {
AnyCamera::Camera2D(cam2d) => {
let inv_aspect = if vp[0] > 0.0 { vp[1] / vp[0] } else { 1.0 };
cam2d.proj = [
[inv_aspect, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
visuals.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
cam2d.view,
cam2d.proj,
cam2d.transform,
);
}
AnyCamera::Camera3D(cam3d) => {
let aspect = if vp[1] > 0.0 { vp[0] / vp[1] } else { 1.0 };
let (fov_y_deg, z_near, z_far) = self
.camera3d_components
.get(&active_handle)
.and_then(|cid| world.get_component_by_id_as::<Camera3DComponent>(*cid))
.map(|c| (c.fov_y_degrees, c.z_near, c.z_far))
.unwrap_or((
Camera3DComponent::DEFAULT_FOV_Y_DEGREES,
Camera3DComponent::DEFAULT_Z_NEAR,
Camera3DComponent::DEFAULT_Z_FAR,
));
cam3d.proj =
Camera3D::perspective_rh_zo(fov_y_deg.to_radians(), aspect, z_near, z_far);
visuals.set_camera_mono_for_target_with_transform(
crate::engine::graphics::CameraTarget::Window,
cam3d.view,
cam3d.proj,
cam3d.transform,
);
}
}
}
}
fn transform_from_matrix_world(m: [[f32; 4]; 4]) -> Transform {
let mut t = Transform::default();
t.model = m;
t.matrix_world = m;
t.translation = [m[3][0], m[3][1], m[3][2]];
t
}