use crate::fusion::{FusionInput, FusionOutput};
use crate::scene::{DenseScene, DenseView};
use rayon::prelude::*;
#[derive(Clone, Debug, PartialEq)]
pub struct VisibilityParams {
pub check_views: usize,
pub tau: f64,
pub min_violations: usize,
}
impl Default for VisibilityParams {
fn default() -> Self {
Self { check_views: 0, tau: 0.01, min_violations: 2 }
}
}
impl VisibilityParams {
pub fn enabled(&self) -> bool {
self.check_views > 0 && self.tau > 0.0 && self.min_violations > 0
}
}
#[inline]
fn project_pixel(view: &DenseView, x: &[f64; 3]) -> Option<(f64, usize)> {
let c = view.to_cam(x);
if c[2] <= 0.0 {
return None;
}
let col = (view.k[0] * c[0] / c[2] + view.k[2]).round();
let row = (view.k[1] * c[1] / c[2] + view.k[3]).round();
if col < 0.0 || row < 0.0 || col >= view.width as f64 || row >= view.height as f64 {
return None;
}
Some((c[2], row as usize * view.width + col as usize))
}
fn candidates(vis: &[u32], overlap: &[Vec<usize>], limit: usize, out: &mut Vec<usize>) {
out.clear();
for &v in vis {
let Some(list) = overlap.get(v as usize) else { continue };
for &o in list {
if out.len() >= limit {
return;
}
if !out.contains(&o) {
out.push(o);
}
}
}
}
pub fn filter_visibility(
scene: &DenseScene,
inputs: &[Option<FusionInput<'_>>],
overlap: &[Vec<usize>],
out: &mut FusionOutput,
p: &VisibilityParams,
) -> usize {
let n = out.cloud.len();
if !p.enabled() || n == 0 || out.visibility.len() != n {
return 0;
}
let keep: Vec<bool> = (0..n)
.into_par_iter()
.map_init(Vec::new, |cand, i| {
let q = out.cloud.positions[i];
let x = [q[0] as f64, q[1] as f64, q[2] as f64];
candidates(&out.visibility[i], overlap, p.check_views, cand);
let (mut violations, mut supports) = (0usize, 0usize);
for &v in cand.iter() {
let (Some(view), Some(Some(inp))) = (scene.views.get(v), inputs.get(v)) else { continue };
let Some((dx, pix)) = project_pixel(view, &x) else { continue };
let dv = inp.depth.get(pix).copied().unwrap_or(0.0) as f64;
if dv <= 0.0 {
continue;
}
if dx < dv * (1.0 - p.tau) {
violations += 1;
} else if (dx - dv).abs() <= p.tau * dv {
supports += 1;
}
}
!(violations >= p.min_violations && violations > supports)
})
.collect();
let removed = keep.iter().filter(|&&k| !k).count();
if removed > 0 {
retain_mask(&mut out.cloud.positions, &keep);
retain_mask(&mut out.cloud.normals, &keep);
retain_mask(&mut out.cloud.colors, &keep);
retain_mask(&mut out.cloud.keys, &keep);
retain_mask(&mut out.visibility, &keep);
retain_mask(&mut out.residual, &keep);
}
removed
}
fn retain_mask<T>(v: &mut Vec<T>, keep: &[bool]) {
if v.len() != keep.len() {
return;
}
let mut it = keep.iter();
v.retain(|_| it.next().copied().unwrap_or(true));
}
#[cfg(test)]
mod tests {
use super::*;
use crate::synthetic::{make_scene, SynthBox, SynthConfig, SynthScene};
const PLANE_POINTS: usize = 5;
fn setup() -> (SynthScene, FusionOutput, Vec<Vec<usize>>) {
let cfg = SynthConfig { boxes: vec![SynthBox { min: [-30.0, -30.0, -1.0], max: [30.0, 30.0, 0.0] }], ..SynthConfig::default() };
let s = make_scene(&cfg);
let v0 = &s.scene.views[0];
let mut out = FusionOutput::default();
let pixels = [(150usize, 120usize), (190, 140), (230, 160), (170, 100), (210, 180)];
for frac in [1.0, 0.5] {
for &(col, row) in &pixels {
let d = s.depth[0][row * v0.width + col] as f64;
assert!(d > 0.0);
let c = [(col as f64 - v0.k[2]) / v0.k[0] * d * frac, (row as f64 - v0.k[3]) / v0.k[1] * d * frac, d * frac];
let x = v0.to_world(&c);
out.cloud.positions.push([x[0] as f32, x[1] as f32, x[2] as f32]);
out.cloud.colors.push([128, 128, 128]);
out.visibility.push(vec![0]);
}
}
let nv = s.scene.views.len();
let overlap = (0..nv).map(|v| (0..nv).filter(|&o| o != v).collect()).collect();
(s, out, overlap)
}
fn inputs(s: &SynthScene) -> Vec<Option<FusionInput<'_>>> {
s.depth.iter().zip(&s.normal).map(|(d, n)| Some(FusionInput::plain(d, n))).collect()
}
#[test]
fn removes_floaters_and_keeps_plane() {
let (s, mut out, overlap) = setup();
let inp = inputs(&s);
let p = VisibilityParams { check_views: 8, tau: 0.01, min_violations: 2 };
assert!(p.enabled());
let removed = filter_visibility(&s.scene, &inp, &overlap, &mut out, &p);
assert_eq!(removed, 5);
assert_eq!(out.cloud.len(), PLANE_POINTS);
assert_eq!(out.cloud.colors.len(), PLANE_POINTS);
assert_eq!(out.visibility.len(), PLANE_POINTS);
assert!(out.cloud.positions.iter().all(|q| q[2].abs() < 0.05), "{:?}", out.cloud.positions);
}
#[test]
fn default_params_leave_cloud_unchanged() {
let (s, mut out, overlap) = setup();
let inp = inputs(&s);
let before = out.cloud.positions.clone();
assert!(!VisibilityParams::default().enabled());
assert_eq!(filter_visibility(&s.scene, &inp, &overlap, &mut out, &VisibilityParams::default()), 0);
assert_eq!(out.cloud.positions, before);
assert_eq!(out.visibility.len(), before.len());
}
}