glam 0.33.2

A simple and fast 3D math library for games and graphics
Documentation
// Generated from camera_impl.rs.tera template. Edit the template, not the generated file.

//! Internal primitives for building view and projection matrices.
//!
//! Each function is parameterized over const generics that select the
//! coordinate system convention:
//!
//! * `RH` - right-handed (`true`) vs left-handed (`false`) view space
//! * `ZO` - NDC Z maps to `[0, 1]` (`true`) vs `[-1, 1]` (`false`)
//! * `YFLIP` - NDC Y points down (`true`) vs up (`false`)
//!
//! These are called by the public wrappers in `lh::proj`, `rh::proj`,
//! `lh::view`, and `rh::view` and should not be used directly.

use crate::{f64::math, DAffine3, DMat3, DMat4, DQuat, DVec3, DVec4};

/// Computes an orthonormal view basis from eye, direction, and up.
#[inline(always)]
#[must_use]
fn look_to_axes4<const RH: bool>(eye: DVec3, dir: DVec3, up: DVec3) -> [DVec3; 4] {
    glam_assert!(dir.is_normalized());
    glam_assert!(up.is_normalized());
    let f = if RH { dir } else { -dir };
    let s = f.cross(up).normalize();
    let u = s.cross(f);
    [
        DVec3::new(s.x, u.x, -f.x),
        DVec3::new(s.y, u.y, -f.y),
        DVec3::new(s.z, u.z, -f.z),
        DVec3::new(-eye.dot(s), -eye.dot(u), eye.dot(f)),
    ]
}

/// Same as [`look_to_axes4`] but without the translation row.
#[inline(always)]
#[must_use]
fn look_to_axes3<const RH: bool>(dir: DVec3, up: DVec3) -> [DVec3; 3] {
    glam_assert!(dir.is_normalized());
    glam_assert!(up.is_normalized());
    let f = if RH { dir } else { -dir };
    let s = f.cross(up).normalize();
    let u = s.cross(f);
    [
        DVec3::new(s.x, u.x, -f.x),
        DVec3::new(s.y, u.y, -f.y),
        DVec3::new(s.z, u.z, -f.z),
    ]
}

/// Assembles a `DMat4` view matrix from eye, direction, and up.
#[inline]
#[must_use]
pub(crate) fn look_to_mat4<const RH: bool>(eye: DVec3, dir: DVec3, up: DVec3) -> DMat4 {
    let axes = look_to_axes4::<RH>(eye, dir, up);
    DMat4::from_cols(
        axes[0].extend(0.0),
        axes[1].extend(0.0),
        axes[2].extend(0.0),
        axes[3].extend(1.0),
    )
}

/// Assembles an `DAffine3` view transform from eye, direction, and up.
#[inline]
#[must_use]
pub(crate) fn look_to_affine3<const RH: bool>(eye: DVec3, dir: DVec3, up: DVec3) -> DAffine3 {
    let axes = look_to_axes4::<RH>(eye, dir, up);
    DAffine3 {
        matrix3: DMat3::from_cols(axes[0], axes[1], axes[2]),
        translation: axes[3],
    }
}

/// Returns a `DMat3` view rotation (no translation) from direction and up.
#[inline]
#[must_use]
pub(crate) fn look_to_mat3<const RH: bool>(dir: DVec3, up: DVec3) -> DMat3 {
    let axes = look_to_axes3::<RH>(dir, up);
    DMat3::from_cols(axes[0], axes[1], axes[2])
}

/// Returns a `DQuat` representing a view rotation from direction and up.
#[inline]
#[must_use]
pub(crate) fn look_to_quat<const RH: bool>(dir: DVec3, up: DVec3) -> DQuat {
    let axes = look_to_axes3::<RH>(dir, up);
    DQuat::from_rotation_axes(axes[0], axes[1], axes[2])
}

/// Builds a perspective projection matrix from FOV, aspect, and near/far planes.
///
/// # Panics
///
/// Will panic if `near` or `far` are <= 0 when `glam_assert` is enabled.
#[inline]
#[must_use]
pub(crate) fn perspective<const RH: bool, const ZO: bool, const YFLIP: bool>(
    vertical_fov: f64,
    aspect_ratio: f64,
    near: f64,
    far: f64,
) -> DMat4 {
    glam_assert!(near > 0.0 && far > 0.0);
    let (sin_fov, cos_fov) = math::sin_cos(0.5 * vertical_fov);
    let h = cos_fov / sin_fov;
    let xx = h / aspect_ratio;
    let yy = if YFLIP { -h } else { h };
    let z_range_inv = 1.0 / (far - near);

    let (zz, tz, zw) = match (ZO, RH) {
        (true, false) => (far * z_range_inv, -near * far * z_range_inv, 1.0),
        (false, false) => (
            (far + near) * z_range_inv,
            -2.0 * far * near * z_range_inv,
            1.0,
        ),
        (true, true) => (-far * z_range_inv, -near * far * z_range_inv, -1.0),
        (false, true) => (
            -(far + near) * z_range_inv,
            -2.0 * far * near * z_range_inv,
            -1.0,
        ),
    };

    DMat4::from_cols(
        DVec4::new(xx, 0.0, 0.0, 0.0),
        DVec4::new(0.0, yy, 0.0, 0.0),
        DVec4::new(0.0, 0.0, zz, zw),
        DVec4::new(0.0, 0.0, tz, 0.0),
    )
}

/// Like [`perspective`] but with an infinite far plane.
///
/// Depth approaches 1 as distance -> infinity.
///
/// # Panics
///
/// Will panic if `near` <= 0 when `glam_assert` is enabled.
#[inline]
#[must_use]
pub(crate) fn perspective_infinite<const RH: bool, const ZO: bool, const YFLIP: bool>(
    vertical_fov: f64,
    aspect_ratio: f64,
    near: f64,
) -> DMat4 {
    glam_assert!(near > 0.0);
    let (sin_fov, cos_fov) = math::sin_cos(0.5 * vertical_fov);
    let h = cos_fov / sin_fov;
    let xx = h / aspect_ratio;
    let yy = if YFLIP { -h } else { h };
    let zz = if RH { -1.0 } else { 1.0 };
    let zw = zz;
    let tz = if ZO { -near } else { -2.0 * near };
    DMat4::from_cols(
        DVec4::new(xx, 0.0, 0.0, 0.0),
        DVec4::new(0.0, yy, 0.0, 0.0),
        DVec4::new(0.0, 0.0, zz, zw),
        DVec4::new(0.0, 0.0, tz, 0.0),
    )
}

/// Infinite perspective with reversed Z: near maps to depth 1, infinity to 0.
///
/// Improves floating-point depth precision for distant objects.
///
/// # Panics
///
/// Will panic if `near` <= 0 when `glam_assert` is enabled.
#[inline]
#[must_use]
pub(crate) fn perspective_infinite_reverse<const RH: bool, const YFLIP: bool>(
    vertical_fov: f64,
    aspect_ratio: f64,
    near: f64,
) -> DMat4 {
    glam_assert!(near > 0.0);
    let (sin_fov, cos_fov) = math::sin_cos(0.5 * vertical_fov);
    let h = cos_fov / sin_fov;
    let xx = h / aspect_ratio;
    let yy = if YFLIP { -h } else { h };
    let zw = if RH { -1.0 } else { 1.0 };
    let tz = near;
    DMat4::from_cols(
        DVec4::new(xx, 0.0, 0.0, 0.0),
        DVec4::new(0.0, yy, 0.0, 0.0),
        DVec4::new(0.0, 0.0, 0.0, zw),
        DVec4::new(0.0, 0.0, tz, 0.0),
    )
}

/// Builds an orthographic projection from left, right, bottom, top, near, far bounds.
#[inline]
#[must_use]
pub(crate) fn orthographic<const RH: bool, const ZO: bool, const YFLIP: bool>(
    left: f64,
    right: f64,
    bottom: f64,
    top: f64,
    near: f64,
    far: f64,
) -> DMat4 {
    let width_inv = 1.0 / (right - left);
    let height_inv = 1.0 / (top - bottom);
    let depth_inv = 1.0 / (far - near);
    let xx = width_inv + width_inv;
    let yy = if YFLIP {
        -(height_inv + height_inv)
    } else {
        height_inv + height_inv
    };
    let zz = match (RH, ZO) {
        (true, true) => -depth_inv,
        (false, true) => depth_inv,
        (true, false) => -2.0 * depth_inv,
        (false, false) => 2.0 * depth_inv,
    };
    let tx = -(left + right) * width_inv;
    let ty = -(top + bottom) * height_inv;
    let tz = if ZO {
        -near * depth_inv
    } else {
        -(far + near) * depth_inv
    };
    DMat4::from_cols(
        DVec4::new(xx, 0.0, 0.0, 0.0),
        DVec4::new(0.0, yy, 0.0, 0.0),
        DVec4::new(0.0, 0.0, zz, 0.0),
        DVec4::new(tx, ty, tz, 1.0),
    )
}

/// Builds a perspective projection from a general frustum.
///
/// # Panics
///
/// Will panic if `near` or `far` are <= 0 when `glam_assert` is enabled.
#[inline]
#[must_use]
pub(crate) fn frustum<const RH: bool, const ZO: bool, const YFLIP: bool>(
    left: f64,
    right: f64,
    bottom: f64,
    top: f64,
    near: f64,
    far: f64,
) -> DMat4 {
    glam_assert!(near > 0.0 && far > 0.0);
    let inv_width = 1.0 / (right - left);
    let inv_height = 1.0 / (top - bottom);
    let inv_depth = 1.0 / (far - near);
    let two_near = 2.0 * near;
    let xx = two_near * inv_width;
    let yy = if YFLIP {
        -two_near * inv_height
    } else {
        two_near * inv_height
    };
    let zx = (right + left) * inv_width;
    let zy = if YFLIP {
        -(top + bottom) * inv_height
    } else {
        (top + bottom) * inv_height
    };
    let (zz, zw, tz) = match (RH, ZO) {
        (true, true) => (-far * inv_depth, -1.0, -(far * near) * inv_depth),
        (false, true) => (far * inv_depth, 1.0, -(far * near) * inv_depth),
        (true, false) => (
            -(far + near) * inv_depth,
            -1.0,
            -(2.0 * far * near) * inv_depth,
        ),
        (false, false) => (
            (far + near) * inv_depth,
            1.0,
            -(2.0 * far * near) * inv_depth,
        ),
    };
    DMat4::from_cols(
        DVec4::new(xx, 0.0, 0.0, 0.0),
        DVec4::new(0.0, yy, 0.0, 0.0),
        DVec4::new(zx, zy, zz, zw),
        DVec4::new(0.0, 0.0, tz, 0.0),
    )
}