use crate::camera::CameraExtrinsics;
use crate::camera::CameraIntrinsics;
use crate::error::{Result, VisionError};
#[derive(Debug, Clone)]
pub struct PositionalEncoding {
pub num_levels: usize,
}
impl PositionalEncoding {
pub fn new(num_levels: usize) -> Self {
Self { num_levels }
}
pub fn output_dim(&self, input_dim: usize) -> usize {
2 * self.num_levels * input_dim
}
pub fn encode_scalar(&self, value: f64) -> Vec<f64> {
let mut encoded = Vec::with_capacity(2 * self.num_levels);
for l in 0..self.num_levels {
let freq = std::f64::consts::PI * (1u64 << l) as f64;
let angle = freq * value;
encoded.push(angle.sin());
encoded.push(angle.cos());
}
encoded
}
pub fn encode(&self, values: &[f64]) -> Vec<f64> {
let mut encoded = Vec::with_capacity(self.output_dim(values.len()));
for &v in values {
encoded.extend(self.encode_scalar(v));
}
encoded
}
}
#[derive(Debug, Clone)]
pub struct Ray {
pub origin: [f64; 3],
pub direction: [f64; 3],
}
impl Ray {
pub fn new(origin: [f64; 3], direction: [f64; 3]) -> Self {
let len = (direction[0] * direction[0]
+ direction[1] * direction[1]
+ direction[2] * direction[2])
.sqrt()
.max(1e-15);
Self {
origin,
direction: [direction[0] / len, direction[1] / len, direction[2] / len],
}
}
pub fn at(&self, t: f64) -> [f64; 3] {
[
self.origin[0] + t * self.direction[0],
self.origin[1] + t * self.direction[1],
self.origin[2] + t * self.direction[2],
]
}
}
#[derive(Debug, Clone)]
pub struct RayBundle {
pub rays: Vec<Ray>,
pub width: usize,
pub height: usize,
}
pub fn generate_rays(
intrinsics: &CameraIntrinsics,
extrinsics: &CameraExtrinsics,
width: usize,
height: usize,
) -> RayBundle {
let rt = transpose3(&extrinsics.rotation);
let neg_t = [
-extrinsics.translation[0],
-extrinsics.translation[1],
-extrinsics.translation[2],
];
let cam_pos = mat3_vec3_mul(&rt, neg_t);
let mut rays = Vec::with_capacity(width * height);
for row in 0..height {
for col in 0..width {
let xn = (col as f64 + 0.5 - intrinsics.cx) / intrinsics.fx;
let yn = (row as f64 + 0.5 - intrinsics.cy) / intrinsics.fy;
let dir_cam = [xn, yn, 1.0];
let dir_world = mat3_vec3_mul(&rt, dir_cam);
rays.push(Ray::new(cam_pos, dir_world));
}
}
RayBundle {
rays,
width,
height,
}
}
#[derive(Debug, Clone)]
pub struct NeRFConfig {
pub pos_encoding_levels: usize,
pub dir_encoding_levels: usize,
pub hidden_dim: usize,
pub num_layers: usize,
pub skip_layer: usize,
pub num_coarse_samples: usize,
pub num_fine_samples: usize,
pub near: f64,
pub far: f64,
}
impl Default for NeRFConfig {
fn default() -> Self {
Self {
pos_encoding_levels: 10,
dir_encoding_levels: 4,
hidden_dim: 256,
num_layers: 8,
skip_layer: 4,
num_coarse_samples: 64,
num_fine_samples: 128,
near: 2.0,
far: 6.0,
}
}
}
#[derive(Debug, Clone)]
pub struct NeRFModel {
pub config: NeRFConfig,
pub pos_encoder: PositionalEncoding,
pub dir_encoder: PositionalEncoding,
weights: Vec<Vec<f64>>,
biases: Vec<Vec<f64>>,
sigma_weight: Vec<f64>,
sigma_bias: f64,
color_weight: Vec<f64>,
color_bias: [f64; 3],
}
impl NeRFModel {
pub fn new(config: NeRFConfig) -> Self {
let pos_encoder = PositionalEncoding::new(config.pos_encoding_levels);
let dir_encoder = PositionalEncoding::new(config.dir_encoding_levels);
let pos_dim = pos_encoder.output_dim(3);
let _dir_dim = dir_encoder.output_dim(3);
let hidden = config.hidden_dim;
let num_layers = config.num_layers;
let skip = config.skip_layer;
let mut weights: Vec<Vec<f64>> = Vec::with_capacity(num_layers);
let mut biases: Vec<Vec<f64>> = Vec::with_capacity(num_layers);
for i in 0..num_layers {
let in_dim = if i == 0 {
pos_dim
} else if i == skip {
hidden + pos_dim } else {
hidden
};
let scale = (2.0 / (in_dim + hidden) as f64).sqrt();
let w: Vec<f64> = (0..in_dim * hidden)
.map(|j| {
let seed = (i * 10007 + j * 31 + 17) as f64;
scale * (seed.sin() * 0.5)
})
.collect();
let b = vec![0.0; hidden];
weights.push(w);
biases.push(b);
}
let sigma_weight: Vec<f64> = (0..hidden).map(|j| 0.01 * (j as f64).cos()).collect();
let sigma_bias = 0.0;
let color_input_dim = hidden + dir_encoder.output_dim(3);
let color_weight: Vec<f64> = (0..color_input_dim * 3)
.map(|j| 0.01 * (j as f64 * 0.7).sin())
.collect();
let color_bias = [0.5, 0.5, 0.5];
Self {
config,
pos_encoder,
dir_encoder,
weights,
biases,
sigma_weight,
sigma_bias,
color_weight,
color_bias,
}
}
pub fn forward(&self, position: &[f64; 3], direction: &[f64; 3]) -> (f64, [f64; 3]) {
let pos_enc = self.pos_encoder.encode(position);
let dir_enc = self.dir_encoder.encode(direction);
let hidden = self.config.hidden_dim;
let skip = self.config.skip_layer;
let mut h = pos_enc.clone();
for i in 0..self.config.num_layers {
if i == skip {
let mut combined = h.clone();
combined.extend_from_slice(&pos_enc);
h = combined;
}
let in_dim = h.len();
let w = &self.weights[i];
let b = &self.biases[i];
let mut out = vec![0.0; hidden];
for o in 0..hidden {
let mut sum = b[o];
for k in 0..in_dim {
sum += h[k] * w[k * hidden + o];
}
out[o] = sum.max(0.0);
}
h = out;
}
let mut sigma = self.sigma_bias;
for (j, &hj) in h.iter().enumerate() {
sigma += hj * self.sigma_weight[j];
}
sigma = sigma.max(0.0);
let mut color_input = h;
color_input.extend_from_slice(&dir_enc);
let cin_dim = color_input.len();
let mut rgb = [0.0_f64; 3];
for (c, rgb_val) in rgb.iter_mut().enumerate() {
let mut sum = self.color_bias[c];
for (k, &ci) in color_input.iter().enumerate().take(cin_dim) {
sum += ci * self.color_weight[k * 3 + c];
}
*rgb_val = 1.0 / (1.0 + (-sum).exp());
}
(sigma, rgb)
}
}
#[derive(Debug, Clone)]
pub struct VolumeRenderResult {
pub color: [f64; 3],
pub depth: f64,
pub opacity: f64,
pub weights: Vec<f64>,
}
pub fn volume_render(
sigmas: &[f64],
colors: &[[f64; 3]],
deltas: &[f64],
) -> Result<VolumeRenderResult> {
let n = sigmas.len();
if colors.len() != n || deltas.len() != n {
return Err(VisionError::InvalidParameter(
"sigmas, colors, and deltas must have the same length".to_string(),
));
}
if n == 0 {
return Ok(VolumeRenderResult {
color: [0.0; 3],
depth: 0.0,
opacity: 0.0,
weights: Vec::new(),
});
}
let mut accumulated_color = [0.0_f64; 3];
let mut accumulated_depth = 0.0_f64;
let mut transmittance = 1.0_f64;
let mut accumulated_opacity = 0.0_f64;
let mut weights = Vec::with_capacity(n);
let mut t = 0.0_f64;
for i in 0..n {
let alpha = 1.0 - (-sigmas[i] * deltas[i]).exp();
let weight = transmittance * alpha;
accumulated_color[0] += weight * colors[i][0];
accumulated_color[1] += weight * colors[i][1];
accumulated_color[2] += weight * colors[i][2];
let t_mid = t + deltas[i] * 0.5;
accumulated_depth += weight * t_mid;
accumulated_opacity += weight;
weights.push(weight);
transmittance *= 1.0 - alpha;
t += deltas[i];
if transmittance < 1e-10 {
weights.resize(n, 0.0);
break;
}
}
Ok(VolumeRenderResult {
color: accumulated_color,
depth: accumulated_depth,
opacity: accumulated_opacity,
weights,
})
}
#[derive(Debug, Clone)]
pub struct HierarchicalSampler {
pub num_coarse: usize,
pub num_fine: usize,
pub near: f64,
pub far: f64,
}
impl HierarchicalSampler {
pub fn new(num_coarse: usize, num_fine: usize, near: f64, far: f64) -> Self {
Self {
num_coarse,
num_fine,
near,
far,
}
}
pub fn coarse_samples(&self) -> Vec<f64> {
let n = self.num_coarse;
if n == 0 {
return Vec::new();
}
let step = (self.far - self.near) / n as f64;
(0..n)
.map(|i| {
let lo = self.near + i as f64 * step;
lo + step * 0.5
})
.collect()
}
pub fn fine_samples(&self, coarse_ts: &[f64], weights: &[f64]) -> Result<Vec<f64>> {
let n = coarse_ts.len();
if weights.len() != n {
return Err(VisionError::InvalidParameter(
"coarse_ts and weights must have the same length".to_string(),
));
}
if n == 0 || self.num_fine == 0 {
return Ok(Vec::new());
}
let eps = 1e-5;
let total: f64 = weights.iter().sum::<f64>() + eps * n as f64;
let mut cdf = Vec::with_capacity(n + 1);
cdf.push(0.0);
let mut cumsum = 0.0;
for &w in weights {
cumsum += (w + eps) / total;
cdf.push(cumsum);
}
if let Some(last) = cdf.last_mut() {
*last = 1.0;
}
let step = if n > 1 {
coarse_ts.get(1).copied().unwrap_or(self.far) - coarse_ts[0]
} else {
self.far - self.near
};
let mut bin_edges: Vec<f64> = coarse_ts.iter().map(|&t| t - step * 0.5).collect();
bin_edges.push(coarse_ts.last().copied().unwrap_or(self.far) + step * 0.5);
let mut fine_ts = Vec::with_capacity(self.num_fine);
for i in 0..self.num_fine {
let u = (i as f64 + 0.5) / self.num_fine as f64;
let mut lo = 0;
let mut hi = cdf.len() - 1;
while lo < hi {
let mid = (lo + hi) / 2;
if cdf[mid] < u {
lo = mid + 1;
} else {
hi = mid;
}
}
let idx = lo.saturating_sub(1).min(n - 1);
let cdf_lo = cdf[idx];
let cdf_hi = cdf[idx + 1];
let denom = cdf_hi - cdf_lo;
let frac = if denom > 1e-15 {
(u - cdf_lo) / denom
} else {
0.5
};
let t = bin_edges[idx] + frac * (bin_edges[idx + 1] - bin_edges[idx]);
fine_ts.push(t.clamp(self.near, self.far));
}
fine_ts.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(fine_ts)
}
pub fn merge_and_deltas(&self, coarse_ts: &[f64], fine_ts: &[f64]) -> (Vec<f64>, Vec<f64>) {
let mut all: Vec<f64> = coarse_ts.iter().chain(fine_ts.iter()).copied().collect();
all.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
all.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
let n = all.len();
let deltas: Vec<f64> = if n <= 1 {
vec![self.far - self.near; n]
} else {
(0..n)
.map(|i| {
if i + 1 < n {
all[i + 1] - all[i]
} else {
(self.far - all[i]).max(1e-6)
}
})
.collect()
};
(all, deltas)
}
}
pub fn camera_from_look_at(
position: [f64; 3],
target: [f64; 3],
up: [f64; 3],
) -> Result<CameraExtrinsics> {
let fwd = [
target[0] - position[0],
target[1] - position[1],
target[2] - position[2],
];
let fwd_len = vec3_len(fwd);
if fwd_len < 1e-12 {
return Err(VisionError::InvalidParameter(
"position and target are too close".to_string(),
));
}
let fwd = [fwd[0] / fwd_len, fwd[1] / fwd_len, fwd[2] / fwd_len];
let right = cross3(fwd, up);
let right_len = vec3_len(right);
if right_len < 1e-12 {
return Err(VisionError::InvalidParameter(
"up vector is parallel to the look direction".to_string(),
));
}
let right = [
right[0] / right_len,
right[1] / right_len,
right[2] / right_len,
];
let true_up = cross3(right, fwd);
let rotation = [
[right[0], right[1], right[2]],
[-true_up[0], -true_up[1], -true_up[2]],
[fwd[0], fwd[1], fwd[2]],
];
let translation = [
-(rotation[0][0] * position[0]
+ rotation[0][1] * position[1]
+ rotation[0][2] * position[2]),
-(rotation[1][0] * position[0]
+ rotation[1][1] * position[1]
+ rotation[1][2] * position[2]),
-(rotation[2][0] * position[0]
+ rotation[2][1] * position[1]
+ rotation[2][2] * position[2]),
];
Ok(CameraExtrinsics::new(rotation, translation))
}
#[inline]
fn vec3_len(v: [f64; 3]) -> f64 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
#[inline]
fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
#[inline]
fn transpose3(m: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
[
[m[0][0], m[1][0], m[2][0]],
[m[0][1], m[1][1], m[2][1]],
[m[0][2], m[1][2], m[2][2]],
]
}
#[inline]
fn mat3_vec3_mul(m: &[[f64; 3]; 3], v: [f64; 3]) -> [f64; 3] {
[
m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_positional_encoding_output_dim() {
let enc = PositionalEncoding::new(10);
assert_eq!(enc.output_dim(3), 60);
let enc4 = PositionalEncoding::new(4);
assert_eq!(enc4.output_dim(3), 24);
}
#[test]
fn test_positional_encoding_values() {
let enc = PositionalEncoding::new(2);
let encoded = enc.encode_scalar(1.0);
assert_eq!(encoded.len(), 4);
assert!(encoded[0].abs() < 1e-10, "sin(pi) = {}", encoded[0]);
assert!((encoded[1] + 1.0).abs() < 1e-10, "cos(pi) = {}", encoded[1]);
assert!(encoded[2].abs() < 1e-10, "sin(2pi) = {}", encoded[2]);
assert!(
(encoded[3] - 1.0).abs() < 1e-10,
"cos(2pi) = {}",
encoded[3]
);
}
#[test]
fn test_positional_encoding_vector() {
let enc = PositionalEncoding::new(3);
let encoded = enc.encode(&[0.5, 1.0]);
assert_eq!(encoded.len(), enc.output_dim(2)); }
#[test]
fn test_volume_render_opaque_object() {
let sigmas = vec![100.0, 0.0, 0.0, 0.0];
let colors = vec![
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 1.0, 1.0],
];
let deltas = vec![0.1, 0.1, 0.1, 0.1];
let result =
volume_render(&sigmas, &colors, &deltas).expect("volume_render should succeed");
assert!(result.color[0] > 0.99, "R = {}", result.color[0]);
assert!(result.color[1] < 0.01, "G = {}", result.color[1]);
assert!(result.color[2] < 0.01, "B = {}", result.color[2]);
}
#[test]
fn test_volume_render_transmittance_sums() {
let n = 100;
let sigma = 2.0;
let delta = 0.05; let sigmas = vec![sigma; n];
let colors: Vec<[f64; 3]> = vec![[0.5, 0.5, 0.5]; n];
let deltas = vec![delta; n];
let result =
volume_render(&sigmas, &colors, &deltas).expect("volume_render should succeed");
let weight_sum: f64 = result.weights.iter().sum();
assert!(
(weight_sum - 1.0).abs() < 0.01,
"weight_sum = {}",
weight_sum
);
}
#[test]
fn test_volume_render_empty() {
let result =
volume_render(&[], &[], &[]).expect("volume_render should succeed for empty input");
assert_eq!(result.color, [0.0; 3]);
assert_eq!(result.opacity, 0.0);
}
#[test]
fn test_volume_render_mismatched_lengths() {
assert!(volume_render(&[1.0], &[], &[0.1]).is_err());
}
#[test]
fn test_ray_generation_center_pixel_direction() {
let intrinsics = CameraIntrinsics::ideal(100.0, 100.0, 50.0, 50.0);
let extrinsics = CameraExtrinsics::identity();
let bundle = generate_rays(&intrinsics, &extrinsics, 100, 100);
assert_eq!(bundle.rays.len(), 10000);
let center_ray = &bundle.rays[50 * 100 + 50];
assert!(
center_ray.direction[2] > 0.99,
"center ray Z = {}",
center_ray.direction[2]
);
assert!(
center_ray.direction[0].abs() < 0.01,
"center ray X = {}",
center_ray.direction[0]
);
assert!(
center_ray.direction[1].abs() < 0.01,
"center ray Y = {}",
center_ray.direction[1]
);
}
#[test]
fn test_ray_at() {
let ray = Ray::new([1.0, 2.0, 3.0], [1.0, 0.0, 0.0]);
let pt = ray.at(5.0);
assert!((pt[0] - 6.0).abs() < 1e-9);
assert!((pt[1] - 2.0).abs() < 1e-9);
assert!((pt[2] - 3.0).abs() < 1e-9);
}
#[test]
fn test_hierarchical_sampler_coarse() {
let sampler = HierarchicalSampler::new(64, 128, 2.0, 6.0);
let coarse = sampler.coarse_samples();
assert_eq!(coarse.len(), 64);
for &t in &coarse {
assert!((2.0..=6.0).contains(&t), "t = {}", t);
}
for i in 1..coarse.len() {
assert!(coarse[i] >= coarse[i - 1]);
}
}
#[test]
fn test_hierarchical_sampler_fine() {
let sampler = HierarchicalSampler::new(8, 16, 2.0, 6.0);
let coarse = sampler.coarse_samples();
let weights = vec![0.01, 0.01, 0.5, 1.0, 1.0, 0.5, 0.01, 0.01];
let fine = sampler
.fine_samples(&coarse, &weights)
.expect("fine_samples should succeed");
assert_eq!(fine.len(), 16);
for &t in &fine {
assert!((2.0..=6.0).contains(&t), "t = {}", t);
}
}
#[test]
fn test_hierarchical_merge_and_deltas() {
let sampler = HierarchicalSampler::new(4, 4, 0.0, 4.0);
let coarse = sampler.coarse_samples();
let fine = vec![0.3, 1.2, 2.1, 3.5];
let (merged, deltas) = sampler.merge_and_deltas(&coarse, &fine);
assert_eq!(merged.len(), deltas.len());
for i in 1..merged.len() {
assert!(merged[i] >= merged[i - 1]);
}
for &d in &deltas {
assert!(d > 0.0, "delta = {}", d);
}
}
#[test]
fn test_nerf_model_forward() {
let config = NeRFConfig {
hidden_dim: 32,
num_layers: 4,
skip_layer: 2,
pos_encoding_levels: 4,
dir_encoding_levels: 2,
..NeRFConfig::default()
};
let model = NeRFModel::new(config);
let (sigma, rgb) = model.forward(&[0.5, 0.5, 0.5], &[0.0, 0.0, 1.0]);
assert!(sigma >= 0.0, "sigma = {}", sigma);
for (c, &val) in rgb.iter().enumerate().take(3) {
assert!((0.0..=1.0).contains(&val), "rgb[{}] = {}", c, val);
}
}
#[test]
fn test_camera_from_look_at() {
let ext = camera_from_look_at([0.0, 0.0, -5.0], [0.0, 0.0, 0.0], [0.0, -1.0, 0.0])
.expect("camera_from_look_at should succeed");
assert!(
(ext.rotation[2][2] - 1.0).abs() < 1e-9,
"R[2][2] = {}",
ext.rotation[2][2]
);
}
#[test]
fn test_camera_from_look_at_degenerate() {
assert!(camera_from_look_at([0.0; 3], [0.0; 3], [0.0, 1.0, 0.0]).is_err());
}
#[test]
fn test_camera_project_unproject_roundtrip() {
let intrinsics = CameraIntrinsics::ideal(800.0, 800.0, 320.0, 240.0);
let extrinsics = CameraExtrinsics::identity();
let bundle = generate_rays(&intrinsics, &extrinsics, 640, 480);
let ray = &bundle.rays[100 * 640 + 200];
let pt3d = ray.at(5.0);
let px = intrinsics
.project([pt3d[0], pt3d[1], pt3d[2]])
.expect("project should succeed");
assert!(
(px[0] - 200.5).abs() < 0.6,
"u = {}, expected ~200.5",
px[0]
);
assert!(
(px[1] - 100.5).abs() < 0.6,
"v = {}, expected ~100.5",
px[1]
);
}
}