oxicuda_nerf/rendering/
ray.rs1use crate::error::{NerfError, NerfResult};
4
5#[derive(Debug, Clone, Copy)]
9pub struct Ray {
10 pub origin: [f32; 3],
12 pub dir: [f32; 3],
14}
15
16impl Ray {
17 pub fn new(origin: [f32; 3], dir: [f32; 3]) -> NerfResult<Self> {
23 let len_sq = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2];
24 if len_sq < 1e-16 {
25 return Err(NerfError::ZeroRayDirection);
26 }
27 Ok(Self { origin, dir })
28 }
29
30 pub fn normalized(origin: [f32; 3], dir: [f32; 3]) -> NerfResult<Self> {
36 let len_sq = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2];
37 if len_sq < 1e-16 {
38 return Err(NerfError::ZeroRayDirection);
39 }
40 let inv_len = 1.0 / len_sq.sqrt();
41 let ndir = [dir[0] * inv_len, dir[1] * inv_len, dir[2] * inv_len];
42 Ok(Self { origin, dir: ndir })
43 }
44
45 #[must_use]
47 pub fn at(&self, t: f32) -> [f32; 3] {
48 [
49 self.origin[0] + t * self.dir[0],
50 self.origin[1] + t * self.dir[1],
51 self.origin[2] + t * self.dir[2],
52 ]
53 }
54}
55
56#[derive(Debug, Clone, Copy)]
60pub struct PinholeCamera {
61 pub fx: f32,
63 pub fy: f32,
65 pub cx: f32,
67 pub cy: f32,
69 pub width: u32,
71 pub height: u32,
73}
74
75impl PinholeCamera {
76 pub fn new(fx: f32, fy: f32, cx: f32, cy: f32, w: u32, h: u32) -> NerfResult<Self> {
82 if fx <= 0.0 || fy <= 0.0 {
83 return Err(NerfError::InvalidCameraIntrinsics {
84 msg: "focal lengths must be positive".into(),
85 });
86 }
87 if w == 0 || h == 0 {
88 return Err(NerfError::InvalidCameraIntrinsics {
89 msg: "image dimensions must be > 0".into(),
90 });
91 }
92 Ok(Self {
93 fx,
94 fy,
95 cx,
96 cy,
97 width: w,
98 height: h,
99 })
100 }
101
102 pub fn ray_through_pixel(&self, u: f32, v: f32, c2w: &[f32; 12]) -> NerfResult<Ray> {
115 let dx = (u - self.cx) / self.fx;
117 let dy = (v - self.cy) / self.fy;
118 let dz = 1.0_f32;
119
120 let wx = c2w[0] * dx + c2w[1] * dy + c2w[2] * dz;
122 let wy = c2w[4] * dx + c2w[5] * dy + c2w[6] * dz;
123 let wz = c2w[8] * dx + c2w[9] * dy + c2w[10] * dz;
124
125 let origin = [c2w[3], c2w[7], c2w[11]];
127
128 Ray::normalized(origin, [wx, wy, wz])
129 }
130
131 pub fn generate_rays(&self, c2w: &[f32; 12]) -> NerfResult<Vec<Ray>> {
139 let n = (self.width * self.height) as usize;
140 let mut rays = Vec::with_capacity(n);
141 for row in 0..self.height {
142 for col in 0..self.width {
143 let u = col as f32 + 0.5;
144 let v = row as f32 + 0.5;
145 rays.push(self.ray_through_pixel(u, v, c2w)?);
146 }
147 }
148 Ok(rays)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 fn identity_c2w() -> [f32; 12] {
157 [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]
158 }
159
160 #[test]
161 fn ray_at_origin() {
162 let r = Ray::new([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]).unwrap();
163 let pt = r.at(2.0);
164 assert!((pt[2] - 2.0).abs() < 1e-6);
165 }
166
167 #[test]
168 fn ray_zero_dir_error() {
169 assert!(Ray::new([0.0; 3], [0.0; 3]).is_err());
170 }
171
172 #[test]
173 fn camera_principal_ray() {
174 let cam = PinholeCamera::new(100.0, 100.0, 50.0, 50.0, 100, 100).unwrap();
175 let ray = cam.ray_through_pixel(50.5, 50.5, &identity_c2w()).unwrap();
176 assert!(ray.dir[2] > 0.0);
178 }
179
180 #[test]
181 fn generate_rays_count() {
182 let cam = PinholeCamera::new(100.0, 100.0, 50.0, 50.0, 4, 3).unwrap();
183 let rays = cam.generate_rays(&identity_c2w()).unwrap();
184 assert_eq!(rays.len(), 12);
185 }
186}