#[macro_use]
mod support;
macro_rules! impl_camera_tests {
($t:ident, $mat4:ident, $mat3:ident, $vec4:ident, $vec3:ident, $camera:ident) => {
struct AxisConfig {
forward: $vec3,
right: $vec3,
up: $vec3,
}
struct NdcConfig {
z_near: $t,
z_far: $t,
y_down: bool,
}
const EYE: $vec3 = $vec3::ZERO;
const NDC_OPENGL: NdcConfig = NdcConfig {
z_near: -1.0,
z_far: 1.0,
y_down: false,
};
const NDC_VULKAN: NdcConfig = NdcConfig {
z_near: 0.0,
z_far: 1.0,
y_down: true,
};
const NDC_DIRECTX: NdcConfig = NdcConfig {
z_near: 0.0,
z_far: 1.0,
y_down: false,
};
const RH_YUP_AXES: AxisConfig = AxisConfig {
forward: $vec3::NEG_Z,
right: $vec3::X,
up: $vec3::Y,
};
const LH_YUP_AXES: AxisConfig = AxisConfig {
forward: $vec3::Z,
right: $vec3::X,
up: $vec3::Y,
};
#[allow(unused)]
const RH_ZUP_AXES: AxisConfig = AxisConfig {
forward: $vec3::Y,
right: $vec3::X,
up: $vec3::Z,
};
#[allow(unused)]
const LH_ZUP_AXES: AxisConfig = AxisConfig {
forward: $vec3::X,
right: $vec3::Y,
up: $vec3::Z,
};
fn check_view(world: &AxisConfig, view: &$mat4) {
let p = (view * (world.right * 5.0).to_homogeneous()).project();
assert_approx_eq!(p.x, 5.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, 0.0, 1e-6);
let p = (view * (world.up * 5.0).to_homogeneous()).project();
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 5.0, 1e-6);
assert_approx_eq!(p.z, 0.0, 1e-6);
let fwd = (view * (world.forward * 5.0).to_homogeneous()).project();
let expected_z = handedness_sign(world) * 5.0;
assert_approx_eq!(fwd.x, 0.0, 1e-6);
assert_approx_eq!(fwd.y, 0.0, 1e-6);
assert_approx_eq!(fwd.z, expected_z, 1e-6);
let behind = (view * (-world.forward * 5.0).to_homogeneous()).project();
assert_approx_eq!(behind.x, 0.0, 1e-6);
assert_approx_eq!(behind.y, 0.0, 1e-6);
assert_approx_eq!(behind.z, -expected_z, 1e-6);
let p = (view * EYE.to_homogeneous()).project();
assert_approx_eq!(p, $vec3::ZERO, 1e-6);
}
fn check_proj_direction(world: &AxisConfig, ndc: &NdcConfig, view: &$mat4, proj: &$mat4) {
let pt = (proj * (view * (world.forward * 5.0).to_homogeneous())).project();
assert_approx_eq!(pt.x, 0.0, 1e-6);
assert_approx_eq!(pt.y, 0.0, 1e-6);
let pt =
(proj * (view * (world.forward * 5.0 + world.right).to_homogeneous())).project();
assert!(pt.x > 0.0);
let pt =
(proj * (view * (world.forward * 5.0 - world.right).to_homogeneous())).project();
assert!(pt.x < 0.0);
let pt = (proj * (view * (world.forward * 5.0 + world.up).to_homogeneous())).project();
if ndc.y_down {
assert!(pt.y < 0.0);
} else {
assert!(pt.y > 0.0);
}
let pt = (proj * (view * (world.forward * 5.0 - world.up).to_homogeneous())).project();
if ndc.y_down {
assert!(pt.y > 0.0);
} else {
assert!(pt.y < 0.0);
}
}
fn check_proj_near_far(world: &AxisConfig, ndc: &NdcConfig, view: &$mat4, proj: &$mat4) {
let pt = (proj * (view * (world.forward * 1.0).to_homogeneous())).project();
assert_approx_eq!(pt.x, 0.0, 1e-6);
assert_approx_eq!(pt.y, 0.0, 1e-6);
assert_approx_eq!(pt.z, ndc.z_near, 1e-6);
let pt = (proj * (view * (world.forward * 10.0).to_homogeneous())).project();
assert_approx_eq!(pt.x, 0.0, 1e-6);
assert_approx_eq!(pt.y, 0.0, 1e-6);
assert_approx_eq!(pt.z, ndc.z_far, 1e-6);
}
fn check_proj_near(world: &AxisConfig, ndc: &NdcConfig, view: &$mat4, proj: &$mat4) {
let pt = (proj * (view * (world.forward * 1.0).to_homogeneous())).project();
assert_approx_eq!(pt.x, 0.0, 1e-6);
assert_approx_eq!(pt.y, 0.0, 1e-6);
assert_approx_eq!(pt.z, ndc.z_near, 1e-6);
}
fn check_proj_reverse_near(
world: &AxisConfig,
ndc: &NdcConfig,
view: &$mat4,
proj: &$mat4,
) {
let pt = (proj * (view * (world.forward * 1.0).to_homogeneous())).project();
assert_approx_eq!(pt.x, 0.0, 1e-6);
assert_approx_eq!(pt.y, 0.0, 1e-6);
assert_approx_eq!(pt.z, ndc.z_far, 1e-6);
}
fn check_view_rotation_mat3(world: &AxisConfig, rot: &$mat3) {
let expected_z = handedness_sign(world) * 5.0;
let p = *rot * (world.right * 5.0);
assert_approx_eq!(p.x, 5.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, 0.0, 1e-6);
let p = *rot * (world.up * 5.0);
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 5.0, 1e-6);
assert_approx_eq!(p.z, 0.0, 1e-6);
let p = *rot * (world.forward * 5.0);
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, expected_z, 1e-6);
let p = *rot * (-world.forward * 5.0);
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, -expected_z, 1e-6);
}
fn handedness_sign(axes: &AxisConfig) -> $t {
axes.right.dot(axes.up.cross(axes.forward)).signum()
}
mod view_rh {
use super::*;
use glam::$camera::rh::view;
mod yup {
use super::*;
impl_view_tests!(RH_YUP_AXES, $mat4);
}
mod zup {
use super::*;
impl_view_tests!(RH_ZUP_AXES, $mat4);
}
}
mod view_lh {
use super::*;
use glam::$camera::lh::view;
mod yup {
use super::*;
impl_view_tests!(LH_YUP_AXES, $mat4);
}
mod zup {
use super::*;
impl_view_tests!(LH_ZUP_AXES, $mat4);
}
}
mod proj_rh {
use super::*;
use glam::$camera::rh::proj;
glam_test!(test_transform_opengl_perspective, {
let fov = $t::to_radians(90.0);
let p = proj::opengl::perspective(fov, 2.0, 5.0, 15.0);
let original = $vec3::new(5.0, 5.0, -15.0);
let projected = p * original.extend(1.0);
assert_approx_eq!(projected, $vec4::new(2.5, 5.0, 15.0, 15.0), 1.0e-6);
});
glam_test!(test_transform_vulkan_perspective, {
let fov = $t::to_radians(90.0);
let p = proj::vulkan::perspective(fov, 2.0, 5.0, 15.0);
let original = $vec3::new(5.0, 5.0, -15.0);
let projected = p * original.extend(1.0);
assert_approx_eq!(projected, $vec4::new(2.5, -5.0, 15.0, 15.0), 1.0e-6);
});
glam_test!(test_transform_directx_perspective, {
let fov = $t::to_radians(90.0);
let p = proj::directx::perspective(fov, 2.0, 5.0, 15.0);
let original = $vec3::new(5.0, 5.0, -15.0);
let projected = p * original.extend(1.0);
assert_approx_eq!(projected, $vec4::new(2.5, 5.0, 15.0, 15.0), 1.0e-6);
});
glam_test!(test_transform_opengl_orthographic, {
let p = proj::opengl::orthographic(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, -1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, -1.0, -1.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(10.0, 10.0, -10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(1.0, 1.0, 1.0, 1.0), 1.0e-6);
});
glam_test!(test_transform_vulkan_orthographic, {
let p = proj::vulkan::orthographic(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, -1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, 1.0, 0.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(10.0, 10.0, -10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(1.0, -1.0, 1.0, 1.0), 1.0e-6);
});
glam_test!(test_transform_directx_orthographic, {
let p = proj::directx::orthographic(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, -1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, -1.0, 0.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(10.0, 10.0, -10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(1.0, 1.0, 1.0, 1.0), 1.0e-6);
});
glam_test!(test_transform_opengl_frustum, {
let p = proj::opengl::frustum(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, -1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, -1.0, -1.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(100.0, 100.0, -10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(10.0, 10.0, 10.0, 10.0), 1.0e-6);
});
glam_test!(test_transform_vulkan_frustum, {
let p = proj::vulkan::frustum(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, -1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, 1.0, 0.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(100.0, 100.0, -10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(10.0, -10.0, 10.0, 10.0), 1.0e-6);
});
glam_test!(test_transform_directx_frustum, {
let p = proj::directx::frustum(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, -1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, -1.0, 0.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(100.0, 100.0, -10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(10.0, 10.0, 10.0, 10.0), 1.0e-6);
});
}
mod proj_lh {
use super::*;
use glam::$camera::lh::proj;
glam_test!(test_perspective_opengl, {
let fov = $t::to_radians(90.0);
let aspect = 2.0;
let near = 5.0;
let far = 15.0;
let p = proj::opengl::perspective(fov, aspect, near, far);
assert_approx_eq!(p.x_axis, $vec4::new(0.5, 0.0, 0.0, 0.0));
assert_approx_eq!(p.y_axis, $vec4::new(0.0, 1.0, 0.0, 0.0));
assert_approx_eq!(p.z_axis, $vec4::new(0.0, 0.0, 2.0, 1.0));
assert_approx_eq!(p.w_axis, $vec4::new(0.0, 0.0, -15.0, 0.0));
});
glam_test!(test_orthographic_opengl, {
let p = proj::opengl::orthographic(-10.0, 10.0, -5.0, 5.0, -10.0, 10.0);
assert_approx_eq!(p.z_axis, $vec4::new(0.0, 0.0, 0.1, 0.0));
assert_approx_eq!(p.w_axis, $vec4::new(0.0, 0.0, 0.0, 1.0));
let _ = proj::opengl::orthographic(1.0, -1.0, 1.0, -1.0, 1.0, 2.0);
should_glam_assert!({ proj::opengl::orthographic(1.0, 1.0, -1.0, 1.0, 1.0, 2.0) });
should_glam_assert!({ proj::opengl::orthographic(-1.0, 1.0, 1.0, 1.0, 1.0, 2.0) });
should_glam_assert!({ proj::opengl::orthographic(-1.0, 1.0, -1.0, 1.0, 1.0, 1.0) });
});
glam_test!(test_frustum_opengl, {
let fov = $t::to_radians(90.0);
let aspect = 2.0;
let near = 5.0;
let far = 15.0;
let f = (0.5 * fov).tan();
let height = near * f;
let width = height * aspect;
let p = proj::opengl::frustum(-width, width, -height, height, near, far);
assert_approx_eq!(p.x_axis, $vec4::new(0.5, 0.0, 0.0, 0.0));
assert_approx_eq!(p.y_axis, $vec4::new(0.0, 1.0, 0.0, 0.0));
assert_approx_eq!(p.z_axis, $vec4::new(0.0, 0.0, 2.0, 1.0));
assert_approx_eq!(p.w_axis, $vec4::new(0.0, 0.0, -15.0, 0.0));
});
glam_test!(test_transform_opengl_perspective, {
let fov = $t::to_radians(90.0);
let p = proj::opengl::perspective(fov, 2.0, 5.0, 15.0);
let original = $vec3::new(5.0, 5.0, 15.0);
let projected = p * original.extend(1.0);
assert_approx_eq!(projected, $vec4::new(2.5, 5.0, 15.0, 15.0), 1.0e-6);
});
glam_test!(test_transform_vulkan_perspective, {
let fov = $t::to_radians(90.0);
let p = proj::vulkan::perspective(fov, 2.0, 5.0, 15.0);
let original = $vec3::new(5.0, 5.0, 15.0);
let projected = p * original.extend(1.0);
assert_approx_eq!(projected, $vec4::new(2.5, -5.0, 15.0, 15.0), 1.0e-6);
});
glam_test!(test_transform_directx_perspective, {
let fov = $t::to_radians(90.0);
let p = proj::directx::perspective(fov, 2.0, 5.0, 15.0);
let original = $vec3::new(5.0, 5.0, 15.0);
let projected = p * original.extend(1.0);
assert_approx_eq!(projected, $vec4::new(2.5, 5.0, 15.0, 15.0), 1.0e-6);
});
glam_test!(test_transform_opengl_orthographic, {
let p = proj::opengl::orthographic(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, 1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, -1.0, -1.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(10.0, 10.0, 10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(1.0, 1.0, 1.0, 1.0), 1.0e-6);
});
glam_test!(test_transform_vulkan_orthographic, {
let p = proj::vulkan::orthographic(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, 1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, 1.0, 0.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(10.0, 10.0, 10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(1.0, -1.0, 1.0, 1.0), 1.0e-6);
});
glam_test!(test_transform_directx_orthographic, {
let p = proj::directx::orthographic(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, 1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, -1.0, 0.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(10.0, 10.0, 10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(1.0, 1.0, 1.0, 1.0), 1.0e-6);
});
glam_test!(test_transform_opengl_frustum, {
let p = proj::opengl::frustum(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, 1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, -1.0, -1.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(100.0, 100.0, 10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(10.0, 10.0, 10.0, 10.0), 1.0e-6);
});
glam_test!(test_transform_vulkan_frustum, {
let p = proj::vulkan::frustum(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, 1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, 1.0, 0.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(100.0, 100.0, 10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(10.0, -10.0, 10.0, 10.0), 1.0e-6);
});
glam_test!(test_transform_directx_frustum, {
let p = proj::directx::frustum(0.0, 10.0, 0.0, 10.0, 1.0, 10.0);
let projected = p * $vec3::new(0.0, 0.0, 1.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(-1.0, -1.0, 0.0, 1.0), 1.0e-6);
let projected = p * $vec3::new(100.0, 100.0, 10.0).extend(1.0);
assert_approx_eq!(projected, $vec4::new(10.0, 10.0, 10.0, 10.0), 1.0e-6);
});
}
mod pipeline_rh {
use super::*;
use glam::$camera::rh::{proj, view};
mod yup {
use super::*;
impl_pipeline_tests!($t, RH_YUP_AXES);
}
mod zup {
use super::*;
impl_pipeline_tests!($t, RH_ZUP_AXES);
}
mod gltf {
use super::*;
const GLTF_AXES: AxisConfig = AxisConfig {
forward: $vec3::Z,
right: $vec3::NEG_X,
up: $vec3::Y,
};
impl_pipeline_tests!($t, GLTF_AXES);
}
}
mod pipeline_lh {
use super::*;
use glam::$camera::lh::{proj, view};
mod yup {
use super::*;
impl_pipeline_tests!($t, LH_YUP_AXES);
}
mod zup {
use super::*;
impl_pipeline_tests!($t, LH_ZUP_AXES);
}
}
};
}
macro_rules! impl_view_tests {
($world:ident, $mat4:ident) => {
glam_test!(test_look_at_mat4, {
let m = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
check_view(&$world, &m);
});
glam_test!(test_look_to_mat4, {
let m = view::look_to_mat4(EYE, $world.forward, $world.up);
check_view(&$world, &m);
});
glam_test!(test_look_at_affine3, {
let a = view::look_at_affine3(EYE, $world.forward * 5.0, $world.up);
let m = $mat4::from(a);
check_view(&$world, &m);
});
glam_test!(test_look_to_affine3, {
let a = view::look_to_affine3(EYE, $world.forward, $world.up);
let m = $mat4::from(a);
check_view(&$world, &m);
});
glam_test!(test_look_at_mat3, {
let rot = view::look_at_mat3(EYE, $world.forward * 5.0, $world.up);
check_view_rotation_mat3(&$world, &rot);
});
glam_test!(test_look_to_mat3, {
let rot = view::look_to_mat3($world.forward, $world.up);
check_view_rotation_mat3(&$world, &rot);
});
glam_test!(test_look_at_quat, {
let q = view::look_at_quat(EYE, $world.forward * 5.0, $world.up);
let expected_z = handedness_sign(&$world) * 5.0;
let p = q * ($world.right * 5.0);
assert_approx_eq!(p.x, 5.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, 0.0, 1e-6);
let p = q * ($world.up * 5.0);
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 5.0, 1e-6);
assert_approx_eq!(p.z, 0.0, 1e-6);
let p = q * ($world.forward * 5.0);
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, expected_z, 1e-6);
let p = q * (-$world.forward * 5.0);
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, -expected_z, 1e-6);
});
glam_test!(test_look_to_quat, {
let q = view::look_to_quat($world.forward, $world.up);
let expected_z = handedness_sign(&$world) * 5.0;
let p = q * ($world.right * 5.0);
assert_approx_eq!(p.x, 5.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, 0.0, 1e-6);
let p = q * ($world.up * 5.0);
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 5.0, 1e-6);
assert_approx_eq!(p.z, 0.0, 1e-6);
let p = q * ($world.forward * 5.0);
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, expected_z, 1e-6);
let p = q * (-$world.forward * 5.0);
assert_approx_eq!(p.x, 0.0, 1e-6);
assert_approx_eq!(p.y, 0.0, 1e-6);
assert_approx_eq!(p.z, -expected_z, 1e-6);
});
};
}
macro_rules! impl_pipeline_tests {
($t:ident, $world:ident) => {
glam_test!(test_opengl_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::opengl::perspective($t::to_radians(90.0), 1.0, 1.0, 10.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_OPENGL, &v, &p);
check_proj_near_far(&$world, &NDC_OPENGL, &v, &p);
});
glam_test!(test_vulkan_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::vulkan::perspective($t::to_radians(90.0), 1.0, 1.0, 10.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_VULKAN, &v, &p);
check_proj_near_far(&$world, &NDC_VULKAN, &v, &p);
});
glam_test!(test_directx_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::directx::perspective($t::to_radians(90.0), 1.0, 1.0, 10.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_DIRECTX, &v, &p);
check_proj_near_far(&$world, &NDC_DIRECTX, &v, &p);
});
glam_test!(test_opengl_orthographic_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::opengl::orthographic(-5.0, 5.0, -5.0, 5.0, 1.0, 10.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_OPENGL, &v, &p);
check_proj_near_far(&$world, &NDC_OPENGL, &v, &p);
should_glam_assert!({ proj::opengl::orthographic(1.0, 1.0, -1.0, 1.0, 1.0, 2.0) });
should_glam_assert!({ proj::opengl::orthographic(-1.0, 1.0, 1.0, 1.0, 1.0, 2.0) });
should_glam_assert!({ proj::opengl::orthographic(-1.0, 1.0, -1.0, 1.0, 1.0, 1.0) });
});
glam_test!(test_vulkan_orthographic_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::vulkan::orthographic(-5.0, 5.0, -5.0, 5.0, 1.0, 10.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_VULKAN, &v, &p);
check_proj_near_far(&$world, &NDC_VULKAN, &v, &p);
should_glam_assert!({ proj::vulkan::orthographic(1.0, 1.0, -1.0, 1.0, 1.0, 2.0) });
should_glam_assert!({ proj::vulkan::orthographic(-1.0, 1.0, 1.0, 1.0, 1.0, 2.0) });
should_glam_assert!({ proj::vulkan::orthographic(-1.0, 1.0, -1.0, 1.0, 1.0, 1.0) });
});
glam_test!(test_directx_orthographic_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::directx::orthographic(-5.0, 5.0, -5.0, 5.0, 1.0, 10.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_DIRECTX, &v, &p);
check_proj_near_far(&$world, &NDC_DIRECTX, &v, &p);
should_glam_assert!({ proj::directx::orthographic(1.0, 1.0, -1.0, 1.0, 1.0, 2.0) });
should_glam_assert!({ proj::directx::orthographic(-1.0, 1.0, 1.0, 1.0, 1.0, 2.0) });
should_glam_assert!({ proj::directx::orthographic(-1.0, 1.0, -1.0, 1.0, 1.0, 1.0) });
});
glam_test!(test_opengl_frustum_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let fov = $t::to_radians(90.0);
let f = (0.5 * fov).tan();
let height = 1.0 * f;
let width = height * 1.0;
let p = proj::opengl::frustum(-width, width, -height, height, 1.0, 10.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_OPENGL, &v, &p);
check_proj_near_far(&$world, &NDC_OPENGL, &v, &p);
});
glam_test!(test_vulkan_frustum_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let fov = $t::to_radians(90.0);
let f = (0.5 * fov).tan();
let height = 1.0 * f;
let width = height * 1.0;
let p = proj::vulkan::frustum(-width, width, -height, height, 1.0, 10.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_VULKAN, &v, &p);
check_proj_near_far(&$world, &NDC_VULKAN, &v, &p);
});
glam_test!(test_directx_frustum_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let fov = $t::to_radians(90.0);
let f = (0.5 * fov).tan();
let height = 1.0 * f;
let width = height * 1.0;
let p = proj::directx::frustum(-width, width, -height, height, 1.0, 10.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_DIRECTX, &v, &p);
check_proj_near_far(&$world, &NDC_DIRECTX, &v, &p);
});
glam_test!(test_vulkan_infinite_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::vulkan::perspective_infinite($t::to_radians(90.0), 1.0, 1.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_VULKAN, &v, &p);
check_proj_near(&$world, &NDC_VULKAN, &v, &p);
});
glam_test!(test_directx_infinite_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::directx::perspective_infinite($t::to_radians(90.0), 1.0, 1.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_DIRECTX, &v, &p);
check_proj_near(&$world, &NDC_DIRECTX, &v, &p);
});
glam_test!(test_vulkan_infinite_reverse_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::vulkan::perspective_infinite_reverse($t::to_radians(90.0), 1.0, 1.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_VULKAN, &v, &p);
check_proj_reverse_near(&$world, &NDC_VULKAN, &v, &p);
});
glam_test!(test_directx_infinite_reverse_pipeline, {
let v = view::look_at_mat4(EYE, $world.forward * 5.0, $world.up);
let p = proj::directx::perspective_infinite_reverse($t::to_radians(90.0), 1.0, 1.0);
check_view(&$world, &v);
check_proj_direction(&$world, &NDC_DIRECTX, &v, &p);
check_proj_reverse_near(&$world, &NDC_DIRECTX, &v, &p);
});
};
}
macro_rules! impl_model_tests {
($vec3:ident, $mat3:ident, $mat4:ident, $quat:ident, $camera:ident) => {
use glam::$camera::model;
use glam::$camera::{lh, rh};
const EPS: f32 = 1e-6;
const DIR: $vec3 = $vec3::new(0.6, 0.0, 0.8);
const EYE: $vec3 = $vec3::new(1.0, 2.0, 3.0);
struct Convention {
world_up: $vec3,
fwd_axis: $vec3,
up_axis: $vec3,
}
const BEVY: Convention = Convention {
world_up: $vec3::Y,
fwd_axis: $vec3::NEG_Z,
up_axis: $vec3::Y,
};
const UNREAL: Convention = Convention {
world_up: $vec3::Z,
fwd_axis: $vec3::X,
up_axis: $vec3::Z,
};
const UNITY: Convention = Convention {
world_up: $vec3::Y,
fwd_axis: $vec3::Z,
up_axis: $vec3::Y,
};
const BLENDER: Convention = Convention {
world_up: $vec3::Z,
fwd_axis: $vec3::Y,
up_axis: $vec3::Z,
};
const CONVENTIONS: [Convention; 4] = [BEVY, UNREAL, UNITY, BLENDER];
fn orthogonalize(v: $vec3, n: $vec3) -> $vec3 {
(v - n * v.dot(n)).normalize()
}
fn rotation(convention: &Convention) -> $mat3 {
model::look_to_mat3(
DIR,
convention.world_up,
convention.fwd_axis,
convention.up_axis,
)
}
fn check_rotation(rot: &$mat3, convention: &Convention) {
assert_approx_eq!(*rot * convention.fwd_axis, DIR, EPS);
assert_approx_eq!(
*rot * convention.up_axis,
orthogonalize(convention.world_up, DIR),
EPS
);
assert_approx_eq!(*rot * rot.transpose(), $mat3::IDENTITY, EPS);
assert_approx_eq!(rot.determinant(), 1.0, EPS);
}
glam_test!(test_look_to_quat_bevy, {
let q = model::look_to_quat(DIR, BEVY.world_up, BEVY.fwd_axis, BEVY.up_axis);
assert_approx_eq!(q * BEVY.fwd_axis, DIR, EPS);
assert_approx_eq!(q * BEVY.up_axis, BEVY.world_up, EPS);
});
glam_test!(test_look_to_quat_unreal, {
let q = model::look_to_quat(DIR, UNREAL.world_up, UNREAL.fwd_axis, UNREAL.up_axis);
assert_approx_eq!(q * UNREAL.fwd_axis, DIR, EPS);
assert_approx_eq!(q * UNREAL.up_axis, orthogonalize(UNREAL.world_up, DIR), EPS);
});
glam_test!(test_look_to_mat3_for_all_conventions, {
for convention in &CONVENTIONS {
check_rotation(&rotation(convention), convention);
}
});
glam_test!(test_look_to_variants_agree, {
for convention in &CONVENTIONS {
let world_up = convention.world_up;
let fwd_axis = convention.fwd_axis;
let up_axis = convention.up_axis;
let rot = rotation(convention);
let quat = model::look_to_quat(DIR, world_up, fwd_axis, up_axis);
let mat = model::look_to_mat4(EYE, DIR, world_up, fwd_axis, up_axis);
let affine = model::look_to_affine3(EYE, DIR, world_up, fwd_axis, up_axis);
assert_approx_eq!($mat3::from_quat(quat), rot, EPS);
assert_approx_eq!($mat4::from(affine), mat, EPS);
assert_approx_eq!(affine.matrix3, rot, EPS);
assert_approx_eq!(affine.translation, EYE, EPS);
assert_approx_eq!(mat.w_axis.truncate(), EYE, EPS);
}
});
glam_test!(test_look_at_matches_look_to, {
let center = EYE + DIR * 5.0;
for convention in &CONVENTIONS {
let world_up = convention.world_up;
let fwd_axis = convention.fwd_axis;
let up_axis = convention.up_axis;
let quat = model::look_to_quat(DIR, world_up, fwd_axis, up_axis);
let at_quat = model::look_at_quat(EYE, center, world_up, fwd_axis, up_axis);
assert_approx_eq!($mat3::from_quat(at_quat), $mat3::from_quat(quat), EPS);
let rot = rotation(convention);
let at_rot = model::look_at_mat3(EYE, center, world_up, fwd_axis, up_axis);
assert_approx_eq!(at_rot, rot, EPS);
let mat = model::look_to_mat4(EYE, DIR, world_up, fwd_axis, up_axis);
let at_mat = model::look_at_mat4(EYE, center, world_up, fwd_axis, up_axis);
assert_approx_eq!(at_mat, mat, EPS);
let affine = model::look_to_affine3(EYE, DIR, world_up, fwd_axis, up_axis);
let at_affine = model::look_at_affine3(EYE, center, world_up, fwd_axis, up_axis);
assert_approx_eq!(at_affine, affine, EPS);
}
});
fn check_view_inverse(model_rot: $mat3, view_rot: $quat) {
assert_approx_eq!(model_rot, $mat3::from_quat(view_rot), EPS);
}
glam_test!(test_look_to_quat_matches_view_inverse_rh, {
let q = model::look_to_quat(DIR, BEVY.world_up, BEVY.fwd_axis, BEVY.up_axis);
check_view_inverse(
$mat3::from_quat(q),
rh::view::look_to_quat(DIR, BEVY.world_up).inverse(),
);
});
glam_test!(test_look_to_quat_matches_view_inverse_lh, {
let q = model::look_to_quat(DIR, UNITY.world_up, UNITY.fwd_axis, UNITY.up_axis);
check_view_inverse(
$mat3::from_quat(q),
lh::view::look_to_quat(DIR, UNITY.world_up).inverse(),
);
});
glam_test!(test_look_to_mat4_translation, {
let mat = model::look_to_mat4(EYE, DIR, BEVY.world_up, BEVY.fwd_axis, BEVY.up_axis);
assert_approx_eq!(mat.w_axis.truncate(), EYE, EPS);
assert_approx_eq!(mat.transform_vector3(BEVY.fwd_axis), DIR, EPS);
assert_approx_eq!(mat.transform_point3($vec3::ZERO), EYE, EPS);
});
glam_test!(test_look_to_glam_assert, {
should_glam_assert!({
model::look_to_quat(DIR * 2.0, BEVY.world_up, BEVY.fwd_axis, BEVY.up_axis)
});
should_glam_assert!({
model::look_to_quat(DIR, BEVY.world_up * 2.0, BEVY.fwd_axis, BEVY.up_axis)
});
should_glam_assert!({
model::look_to_mat3(DIR, BEVY.world_up, BEVY.fwd_axis * 2.0, BEVY.up_axis)
});
should_glam_assert!({
model::look_to_mat4(EYE, DIR, BEVY.world_up, BEVY.fwd_axis, BEVY.up_axis * 2.0)
});
should_glam_assert!({
model::look_to_quat(DIR, BEVY.world_up, BEVY.fwd_axis, BEVY.fwd_axis)
});
});
};
}
mod camera {
use glam::{Mat3, Mat4, Vec3, Vec4};
impl_camera_tests!(f32, Mat4, Mat3, Vec4, Vec3, camera);
mod model {
use glam::{Mat3, Mat4, Quat, Vec3, Vec3A};
impl_model_tests!(Vec3, Mat3, Mat4, Quat, camera);
glam_test!(test_look_to_mat3a_matches_mat3, {
let center = EYE + DIR * 5.0;
for convention in &CONVENTIONS {
let (world_up, fwd_axis, up_axis) =
(convention.world_up, convention.fwd_axis, convention.up_axis);
let rot = rotation(convention);
let rot_a = model::look_to_mat3a(DIR, world_up, fwd_axis, up_axis);
assert_approx_eq!(Mat3::from(rot_a), rot, EPS);
let at_rot_a = model::look_at_mat3a(EYE, center, world_up, fwd_axis, up_axis);
assert_approx_eq!(at_rot_a, rot_a, EPS);
}
});
glam_test!(test_look_to_affine3a_matches_affine3, {
let center = EYE + DIR * 5.0;
for convention in &CONVENTIONS {
let (world_up, fwd_axis, up_axis) =
(convention.world_up, convention.fwd_axis, convention.up_axis);
let affine = model::look_to_affine3(EYE, DIR, world_up, fwd_axis, up_axis);
let affine_a = model::look_to_affine3a(EYE, DIR, world_up, fwd_axis, up_axis);
assert_approx_eq!(Mat4::from(affine_a), Mat4::from(affine), EPS);
assert_approx_eq!(Mat3::from(affine_a.matrix3), affine.matrix3, EPS);
assert_approx_eq!(affine_a.translation, Vec3A::from(affine.translation), EPS);
let at_affine_a = model::look_at_affine3a(EYE, center, world_up, fwd_axis, up_axis);
assert_approx_eq!(at_affine_a, affine_a, EPS);
}
});
}
mod affine3a {
use glam::Vec3;
use glam::camera::lh::view as lh_view;
use glam::camera::rh::view as rh_view;
glam_test!(test_rh_look_at_affine3a, {
let eye = Vec3::new(0.0, 0.0, -5.0);
let center = Vec3::ZERO;
let up = Vec3::new(1.0, 0.0, 0.0);
let point = Vec3::new(1.0, 0.0, 0.0);
let a = rh_view::look_at_affine3a(eye, center, up);
assert_approx_eq!(a.transform_point3(point), Vec3::new(0.0, 1.0, -5.0));
});
glam_test!(test_rh_look_to_affine3a, {
let eye = Vec3::new(0.0, 0.0, -5.0);
let dir = Vec3::new(0.0, 0.0, 1.0);
let up = Vec3::new(1.0, 0.0, 0.0);
let point = Vec3::new(1.0, 0.0, 0.0);
let a = rh_view::look_to_affine3a(eye, dir, up);
assert_approx_eq!(a.transform_point3(point), Vec3::new(0.0, 1.0, -5.0));
});
glam_test!(test_lh_look_at_affine3a, {
let eye = Vec3::new(0.0, 0.0, -5.0);
let center = Vec3::ZERO;
let up = Vec3::new(1.0, 0.0, 0.0);
let point = Vec3::new(1.0, 0.0, 0.0);
let a = lh_view::look_at_affine3a(eye, center, up);
assert_approx_eq!(a.transform_point3(point), Vec3::new(0.0, 1.0, 5.0));
});
glam_test!(test_lh_look_to_affine3a, {
let eye = Vec3::new(0.0, 0.0, -5.0);
let dir = Vec3::new(0.0, 0.0, 1.0);
let up = Vec3::new(1.0, 0.0, 0.0);
let point = Vec3::new(1.0, 0.0, 0.0);
let a = lh_view::look_to_affine3a(eye, dir, up);
assert_approx_eq!(a.transform_point3(point), Vec3::new(0.0, 1.0, 5.0));
});
}
mod mat3a {
use glam::camera::lh::view as lh_view;
use glam::camera::rh::view as rh_view;
use glam::{Vec3, Vec3A};
glam_test!(test_rh_look_at_mat3a, {
let eye = Vec3::new(0.0, 0.0, -5.0);
let center = Vec3::ZERO;
let up = Vec3::new(1.0, 0.0, 0.0);
let rot = rh_view::look_at_mat3a(eye, center, up);
assert_approx_eq!(rot * Vec3A::X, Vec3A::new(0.0, 1.0, 0.0));
});
glam_test!(test_rh_look_to_mat3a, {
let dir = Vec3::new(0.0, 0.0, 1.0);
let up = Vec3::new(1.0, 0.0, 0.0);
let rot = rh_view::look_to_mat3a(dir, up);
assert_approx_eq!(rot * Vec3A::X, Vec3A::new(0.0, 1.0, 0.0));
});
glam_test!(test_lh_look_at_mat3a, {
let eye = Vec3::new(0.0, 0.0, -5.0);
let center = Vec3::ZERO;
let up = Vec3::new(1.0, 0.0, 0.0);
let rot = lh_view::look_at_mat3a(eye, center, up);
assert_approx_eq!(rot * Vec3A::X, Vec3A::new(0.0, 1.0, 0.0));
});
glam_test!(test_lh_look_to_mat3a, {
let dir = Vec3::new(0.0, 0.0, 1.0);
let up = Vec3::new(1.0, 0.0, 0.0);
let rot = lh_view::look_to_mat3a(dir, up);
assert_approx_eq!(rot * Vec3A::X, Vec3A::new(0.0, 1.0, 0.0));
});
}
}
#[cfg(feature = "f64")]
mod dcamera {
use glam::{DMat3, DMat4, DVec3, DVec4};
impl_camera_tests!(f64, DMat4, DMat3, DVec4, DVec3, dcamera);
mod model {
use glam::{DMat3, DMat4, DQuat, DVec3};
impl_model_tests!(DVec3, DMat3, DMat4, DQuat, dcamera);
}
}