use crate::geom::{Location, Transform, Vector3D};
use nalgebra::Matrix3;
pub fn build_projection_matrix(width: u32, height: u32, fov: f32) -> Matrix3<f32> {
let focal = width as f32 / (2.0 * (fov * std::f32::consts::PI / 360.0).tan());
let c_x = width as f32 / 2.0;
let c_y = height as f32 / 2.0;
Matrix3::new(focal, 0.0, c_x, 0.0, focal, c_y, 0.0, 0.0, 1.0)
}
pub fn world_to_camera(point: &Location, camera_transform: &Transform) -> Vector3D {
let na_transform = camera_transform.to_na();
let world_point = nalgebra::Vector4::new(point.x, point.y, point.z, 1.0);
let world_to_camera = na_transform.inverse();
let world_to_camera_matrix = world_to_camera.to_homogeneous();
let camera_point = world_to_camera_matrix * world_point;
Vector3D::new(
camera_point.y, -camera_point.z, camera_point.x, )
}
pub fn project_to_2d(point_3d: &Vector3D, k_matrix: &Matrix3<f32>) -> (f32, f32) {
let na_point = nalgebra::Vector3::new(point_3d.x, point_3d.y, point_3d.z);
let projected = k_matrix * na_point;
let x = projected.x / projected.z;
let y = projected.y / projected.z;
(x, y)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_build_projection_matrix() {
let k = build_projection_matrix(800, 600, 90.0);
assert_relative_eq!(k[(0, 2)], 400.0, epsilon = 1e-5);
assert_relative_eq!(k[(1, 2)], 300.0, epsilon = 1e-5);
assert_relative_eq!(k[(0, 0)], k[(1, 1)], epsilon = 1e-5);
assert_relative_eq!(k[(2, 2)], 1.0, epsilon = 1e-5);
assert_relative_eq!(k[(0, 1)], 0.0, epsilon = 1e-5);
assert_relative_eq!(k[(1, 0)], 0.0, epsilon = 1e-5);
assert_relative_eq!(k[(2, 0)], 0.0, epsilon = 1e-5);
assert_relative_eq!(k[(2, 1)], 0.0, epsilon = 1e-5);
}
#[test]
fn test_project_to_2d_center() {
let k = build_projection_matrix(800, 600, 90.0);
let point = Vector3D::new(0.0, 0.0, 10.0); let (x, y) = project_to_2d(&point, &k);
assert_relative_eq!(x, 400.0, epsilon = 1.0);
assert_relative_eq!(y, 300.0, epsilon = 1.0);
}
#[test]
fn test_project_to_2d_offset() {
let k = build_projection_matrix(800, 600, 90.0);
let point = Vector3D::new(5.0, -2.0, 10.0); let (x, y) = project_to_2d(&point, &k);
assert!(x > 400.0, "x={} should be > 400", x);
assert!(y < 300.0, "y={} should be < 300", y);
}
#[test]
fn test_world_to_camera_identity() {
use crate::geom::Rotation;
let camera_transform = Transform {
location: Location::new(0.0, 0.0, 0.0),
rotation: Rotation::new(0.0, 0.0, 0.0),
};
let world_point = Location {
x: 10.0,
y: 5.0,
z: 2.0,
};
let camera_point = world_to_camera(&world_point, &camera_transform);
assert_relative_eq!(camera_point.x, 5.0, epsilon = 1e-5); assert_relative_eq!(camera_point.y, -2.0, epsilon = 1e-5); assert_relative_eq!(camera_point.z, 10.0, epsilon = 1e-5); }
#[test]
fn test_full_pipeline() {
use crate::geom::Rotation;
let width = 800;
let height = 600;
let fov = 90.0;
let k = build_projection_matrix(width, height, fov);
let camera_transform = Transform {
location: Location::new(0.0, 0.0, 0.0),
rotation: Rotation::new(0.0, 0.0, 0.0),
};
let world_point = Location {
x: 10.0,
y: 0.0,
z: 0.0,
};
let camera_point = world_to_camera(&world_point, &camera_transform);
let (x, y) = project_to_2d(&camera_point, &k);
assert_relative_eq!(x, 400.0, epsilon = 1.0);
assert_relative_eq!(y, 300.0, epsilon = 1.0);
}
}