nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
use crate::ecs::world::{Entity, Mat4, Vec3, Vec4, World};

#[derive(Clone)]
pub struct CameraMatrices {
    pub camera_position: Vec3,
    pub projection: Mat4,
    pub view: Mat4,
}

pub fn query_active_camera_matrices(world: &World) -> Option<CameraMatrices> {
    #[cfg(feature = "openxr")]
    if let Some(xr_override) = &world.resources.xr.camera_override {
        return Some(xr_override.clone());
    }
    let active_camera = world.resources.active_camera?;
    query_camera_matrices(world, active_camera)
}

pub fn query_camera_matrices(world: &World, entity: Entity) -> Option<CameraMatrices> {
    let camera = world.core.get_camera(entity)?;
    let global_transform = world.core.get_global_transform(entity)?;

    let camera_position = global_transform.translation();
    let forward = global_transform.forward_vector();
    let up = global_transform.up_vector();
    let target = camera_position + forward;

    let projection = match &camera.projection {
        crate::ecs::world::components::Projection::Perspective(persp) => {
            let aspect_ratio = persp
                .aspect_ratio
                .or_else(|| query_window_aspect_ratio(world))
                .unwrap_or(16.0 / 9.0);
            persp.matrix_with_aspect(aspect_ratio)
        }
        crate::ecs::world::components::Projection::Orthographic(_) => camera.projection.matrix(),
    };

    let view = nalgebra_glm::look_at(&camera_position, &target, &up);

    Some(CameraMatrices {
        camera_position,
        projection,
        view,
    })
}

pub fn query_window_aspect_ratio(world: &World) -> Option<f32> {
    let (width, height) = world.resources.window.cached_viewport_size?;
    let aspect_ratio = width as f32 / height.max(1) as f32;
    Some(aspect_ratio)
}

#[derive(Clone)]
pub struct CameraFrustumCorners {
    pub near_top_left: Vec3,
    pub near_top_right: Vec3,
    pub near_bottom_left: Vec3,
    pub near_bottom_right: Vec3,
    pub far_top_left: Vec3,
    pub far_top_right: Vec3,
    pub far_bottom_left: Vec3,
    pub far_bottom_right: Vec3,
}

pub fn query_camera_frustum(world: &World, entity: Entity) -> Option<CameraFrustumCorners> {
    let matrices = query_camera_matrices(world, entity)?;
    let view_proj = matrices.projection * matrices.view;
    let inv_view_proj = view_proj.try_inverse()?;

    let near_z = 1.0_f32;
    let far_z = 0.0_f32;

    Some(CameraFrustumCorners {
        near_top_left: unproject_ndc(&inv_view_proj, Vec3::new(-1.0, 1.0, near_z)),
        near_top_right: unproject_ndc(&inv_view_proj, Vec3::new(1.0, 1.0, near_z)),
        near_bottom_left: unproject_ndc(&inv_view_proj, Vec3::new(-1.0, -1.0, near_z)),
        near_bottom_right: unproject_ndc(&inv_view_proj, Vec3::new(1.0, -1.0, near_z)),
        far_top_left: unproject_ndc(&inv_view_proj, Vec3::new(-1.0, 1.0, far_z)),
        far_top_right: unproject_ndc(&inv_view_proj, Vec3::new(1.0, 1.0, far_z)),
        far_bottom_left: unproject_ndc(&inv_view_proj, Vec3::new(-1.0, -1.0, far_z)),
        far_bottom_right: unproject_ndc(&inv_view_proj, Vec3::new(1.0, -1.0, far_z)),
    })
}

fn unproject_ndc(inv_view_proj: &Mat4, ndc: Vec3) -> Vec3 {
    let clip = inv_view_proj * Vec4::new(ndc.x, ndc.y, ndc.z, 1.0);
    Vec3::new(clip.x / clip.w, clip.y / clip.w, clip.z / clip.w)
}