// Generated from {{template_path}} 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.
{% if scalar_t == "f32" %}
{% set vec3_t = "Vec3" %}
{% set vec4_t = "Vec4" %}
{% set vec3a_t = "Vec3A" %}
{% set mat4_t = "Mat4" %}
{% set affine3_t = "Affine3" %}
{% set affine3a_t = "Affine3A" %}
{% set mat3_t = "Mat3" %}
{% set mat3a_t = "Mat3A" %}
{% set quat_t = "Quat" %}
{% set is_f32 = true %}
{% elif scalar_t == "f64" %}
{% set vec3_t = "DVec3" %}
{% set vec4_t = "DVec4" %}
{% set mat4_t = "DMat4" %}
{% set affine3_t = "DAffine3" %}
{% set mat3_t = "DMat3" %}
{% set quat_t = "DQuat" %}
{% set is_f32 = false %}
{% endif %}
use crate::{
{{ vec3_t }}, {{ vec4_t }}, {{ mat4_t }}, {{ mat3_t }}, {{ quat_t }}, {{ affine3_t }},
{% if is_f32 %}
{{ affine3a_t }}, {{ mat3a_t }}, {{ vec3a_t }},
{% endif %}
{{ scalar_t }}::math,
};
/// Computes an orthonormal view basis from eye, direction, and up.
#[inline(always)]
#[must_use]
fn look_to_axes4<const RH: bool>(eye: {{ vec3_t }}, dir: {{ vec3_t }}, up: {{ vec3_t }}) -> [{{ vec3_t }}; 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);
[
{{ vec3_t }}::new(s.x, u.x, -f.x),
{{ vec3_t }}::new(s.y, u.y, -f.y),
{{ vec3_t }}::new(s.z, u.z, -f.z),
{{ vec3_t }}::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: {{ vec3_t }}, up: {{ vec3_t }}) -> [{{ vec3_t }}; 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);
[
{{ vec3_t }}::new(s.x, u.x, -f.x),
{{ vec3_t }}::new(s.y, u.y, -f.y),
{{ vec3_t }}::new(s.z, u.z, -f.z),
]
}
/// Assembles a `{{ mat4_t }}` view matrix from eye, direction, and up.
#[inline]
#[must_use]
pub(crate) fn look_to_mat4<const RH: bool>(eye: {{ vec3_t }}, dir: {{ vec3_t }}, up: {{ vec3_t }}) -> {{ mat4_t }} {
let axes = look_to_axes4::<RH>(eye, dir, up);
{{ mat4_t }}::from_cols(
axes[0].extend(0.0),
axes[1].extend(0.0),
axes[2].extend(0.0),
axes[3].extend(1.0),
)
}
/// Assembles an `{{ affine3_t }}` view transform from eye, direction, and up.
#[inline]
#[must_use]
pub(crate) fn look_to_affine3<const RH: bool>(eye: {{ vec3_t }}, dir: {{ vec3_t }}, up: {{ vec3_t }}) -> {{ affine3_t }} {
let axes = look_to_axes4::<RH>(eye, dir, up);
{{ affine3_t }} {
matrix3: {{ mat3_t }}::from_cols(
axes[0],
axes[1],
axes[2],
),
translation: axes[3],
}
}
{% if is_f32 %}
/// Assembles an `{{ affine3a_t }}` view transform from eye, direction, and up.
#[inline]
#[must_use]
pub(crate) fn look_to_affine3a<const RH: bool>(eye: {{ vec3_t }}, dir: {{ vec3_t }}, up: {{ vec3_t }}) -> {{ affine3a_t }} {
let axes = look_to_axes4::<RH>(eye, dir, up);
{{ affine3a_t }} {
matrix3: {{ mat3a_t }}::from_cols(
{{ vec3a_t }}::from(axes[0]),
{{ vec3a_t }}::from(axes[1]),
{{ vec3a_t }}::from(axes[2]),
),
translation: {{ vec3a_t }}::from(axes[3]),
}
}
/// Returns a `{{ mat3a_t }}` view rotation (no translation) from direction and up.
#[inline]
#[must_use]
pub(crate) fn look_to_mat3a<const RH: bool>(dir: {{ vec3_t }}, up: {{ vec3_t }}) -> {{ mat3a_t }} {
let axes = look_to_axes3::<RH>(dir, up);
{{ mat3a_t }}::from_cols(
{{ vec3a_t }}::from(axes[0]),
{{ vec3a_t }}::from(axes[1]),
{{ vec3a_t }}::from(axes[2]),
)
}
{% endif %}
/// Returns a `{{ mat3_t }}` view rotation (no translation) from direction and up.
#[inline]
#[must_use]
pub(crate) fn look_to_mat3<const RH: bool>(dir: {{ vec3_t }}, up: {{ vec3_t }}) -> {{ mat3_t }} {
let axes = look_to_axes3::<RH>(dir, up);
{{ mat3_t }}::from_cols(
axes[0],
axes[1],
axes[2],
)
}
/// Returns a `{{ quat_t }}` representing a view rotation from direction and up.
#[inline]
#[must_use]
pub(crate) fn look_to_quat<const RH: bool>(dir: {{ vec3_t }}, up: {{ vec3_t }}) -> {{ quat_t }} {
let axes = look_to_axes3::<RH>(dir, up);
{{ quat_t }}::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: {{ scalar_t }}, aspect_ratio: {{ scalar_t }}, near: {{ scalar_t }}, far: {{ scalar_t }}) -> {{ mat4_t }} {
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)
}
};
{{ mat4_t }}::from_cols(
{{ vec4_t }}::new(xx, 0.0, 0.0, 0.0),
{{ vec4_t }}::new(0.0, yy, 0.0, 0.0),
{{ vec4_t }}::new(0.0, 0.0, zz, zw),
{{ vec4_t }}::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: {{ scalar_t }}, aspect_ratio: {{ scalar_t }}, near: {{ scalar_t }}) -> {{ mat4_t }} {
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 };
{{ mat4_t }}::from_cols(
{{ vec4_t }}::new(xx, 0.0, 0.0, 0.0),
{{ vec4_t }}::new(0.0, yy, 0.0, 0.0),
{{ vec4_t }}::new(0.0, 0.0, zz, zw),
{{ vec4_t }}::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: {{ scalar_t }},
aspect_ratio: {{ scalar_t }},
near: {{ scalar_t }},
) -> {{ mat4_t }} {
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;
{{ mat4_t }}::from_cols(
{{ vec4_t }}::new(xx, 0.0, 0.0, 0.0),
{{ vec4_t }}::new(0.0, yy, 0.0, 0.0),
{{ vec4_t }}::new(0.0, 0.0, 0.0, zw),
{{ vec4_t }}::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: {{ scalar_t }},
right: {{ scalar_t }},
bottom: {{ scalar_t }},
top: {{ scalar_t }},
near: {{ scalar_t }},
far: {{ scalar_t }},
) -> {{ mat4_t }} {
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 };
{{ mat4_t }}::from_cols(
{{ vec4_t }}::new(xx, 0.0, 0.0, 0.0),
{{ vec4_t }}::new(0.0, yy, 0.0, 0.0),
{{ vec4_t }}::new(0.0, 0.0, zz, 0.0),
{{ vec4_t }}::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: {{ scalar_t }}, right: {{ scalar_t }}, bottom: {{ scalar_t }}, top: {{ scalar_t }}, near: {{ scalar_t }}, far: {{ scalar_t }}) -> {{ mat4_t }} {
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),
};
{{ mat4_t }}::from_cols(
{{ vec4_t }}::new(xx, 0.0, 0.0, 0.0),
{{ vec4_t }}::new(0.0, yy, 0.0, 0.0),
{{ vec4_t }}::new(zx, zy, zz, zw),
{{ vec4_t }}::new(0.0, 0.0, tz, 0.0),
)
}