use crate::error::{NerfError, NerfResult};
#[derive(Debug, Clone, Copy)]
pub struct Ray {
pub origin: [f32; 3],
pub dir: [f32; 3],
}
impl Ray {
pub fn new(origin: [f32; 3], dir: [f32; 3]) -> NerfResult<Self> {
let len_sq = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2];
if len_sq < 1e-16 {
return Err(NerfError::ZeroRayDirection);
}
Ok(Self { origin, dir })
}
pub fn normalized(origin: [f32; 3], dir: [f32; 3]) -> NerfResult<Self> {
let len_sq = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2];
if len_sq < 1e-16 {
return Err(NerfError::ZeroRayDirection);
}
let inv_len = 1.0 / len_sq.sqrt();
let ndir = [dir[0] * inv_len, dir[1] * inv_len, dir[2] * inv_len];
Ok(Self { origin, dir: ndir })
}
#[must_use]
pub fn at(&self, t: f32) -> [f32; 3] {
[
self.origin[0] + t * self.dir[0],
self.origin[1] + t * self.dir[1],
self.origin[2] + t * self.dir[2],
]
}
}
#[derive(Debug, Clone, Copy)]
pub struct PinholeCamera {
pub fx: f32,
pub fy: f32,
pub cx: f32,
pub cy: f32,
pub width: u32,
pub height: u32,
}
impl PinholeCamera {
pub fn new(fx: f32, fy: f32, cx: f32, cy: f32, w: u32, h: u32) -> NerfResult<Self> {
if fx <= 0.0 || fy <= 0.0 {
return Err(NerfError::InvalidCameraIntrinsics {
msg: "focal lengths must be positive".into(),
});
}
if w == 0 || h == 0 {
return Err(NerfError::InvalidCameraIntrinsics {
msg: "image dimensions must be > 0".into(),
});
}
Ok(Self {
fx,
fy,
cx,
cy,
width: w,
height: h,
})
}
pub fn ray_through_pixel(&self, u: f32, v: f32, c2w: &[f32; 12]) -> NerfResult<Ray> {
let dx = (u - self.cx) / self.fx;
let dy = (v - self.cy) / self.fy;
let dz = 1.0_f32;
let wx = c2w[0] * dx + c2w[1] * dy + c2w[2] * dz;
let wy = c2w[4] * dx + c2w[5] * dy + c2w[6] * dz;
let wz = c2w[8] * dx + c2w[9] * dy + c2w[10] * dz;
let origin = [c2w[3], c2w[7], c2w[11]];
Ray::normalized(origin, [wx, wy, wz])
}
pub fn generate_rays(&self, c2w: &[f32; 12]) -> NerfResult<Vec<Ray>> {
let n = (self.width * self.height) as usize;
let mut rays = Vec::with_capacity(n);
for row in 0..self.height {
for col in 0..self.width {
let u = col as f32 + 0.5;
let v = row as f32 + 0.5;
rays.push(self.ray_through_pixel(u, v, c2w)?);
}
}
Ok(rays)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn identity_c2w() -> [f32; 12] {
[1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]
}
#[test]
fn ray_at_origin() {
let r = Ray::new([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]).unwrap();
let pt = r.at(2.0);
assert!((pt[2] - 2.0).abs() < 1e-6);
}
#[test]
fn ray_zero_dir_error() {
assert!(Ray::new([0.0; 3], [0.0; 3]).is_err());
}
#[test]
fn camera_principal_ray() {
let cam = PinholeCamera::new(100.0, 100.0, 50.0, 50.0, 100, 100).unwrap();
let ray = cam.ray_through_pixel(50.5, 50.5, &identity_c2w()).unwrap();
assert!(ray.dir[2] > 0.0);
}
#[test]
fn generate_rays_count() {
let cam = PinholeCamera::new(100.0, 100.0, 50.0, 50.0, 4, 3).unwrap();
let rays = cam.generate_rays(&identity_c2w()).unwrap();
assert_eq!(rays.len(), 12);
}
}