use super::types::Box3D;
use crate::types::Position3D;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OcclusionMethod {
LineOfSight,
RayCasting,
FresnelZone,
Diffraction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OcclusionMaterial {
pub name: String,
pub transmission: f32,
pub high_freq_absorption: f32,
pub low_freq_absorption: f32,
pub scattering: f32,
}
#[derive(Debug, Clone)]
pub struct OcclusionResult {
pub is_occluded: bool,
pub transmission_factor: f32,
pub high_freq_attenuation: f32,
pub low_freq_attenuation: f32,
pub diffraction_paths: Vec<DiffractionPath>,
}
#[derive(Debug, Clone)]
pub struct DiffractionPath {
pub path: Vec<Position3D>,
pub length: f32,
pub attenuation: f32,
pub delay_samples: usize,
}
pub struct OcclusionDetector {
obstacles: Vec<Box3D>,
method: OcclusionMethod,
materials: std::collections::HashMap<String, OcclusionMaterial>,
}
impl OcclusionDetector {
pub fn new() -> Self {
Self {
obstacles: Vec::new(),
method: OcclusionMethod::LineOfSight,
materials: std::collections::HashMap::new(),
}
}
pub fn add_obstacle(&mut self, obstacle: Box3D) {
self.obstacles.push(obstacle);
}
pub fn add_material(&mut self, material: OcclusionMaterial) {
self.materials.insert(material.name.clone(), material);
}
pub fn check_occlusion(&self, source: Position3D, listener: Position3D) -> OcclusionResult {
match self.method {
OcclusionMethod::LineOfSight => self.line_of_sight_check(source, listener),
OcclusionMethod::RayCasting => self.ray_casting_check(source, listener),
OcclusionMethod::FresnelZone => self.fresnel_zone_check(source, listener),
OcclusionMethod::Diffraction => self.diffraction_check(source, listener),
}
}
fn line_of_sight_check(&self, source: Position3D, listener: Position3D) -> OcclusionResult {
for obstacle in &self.obstacles {
if self.line_intersects_box(source, listener, obstacle) {
let material = self
.materials
.get(&obstacle.material_id)
.cloned()
.unwrap_or_else(OcclusionMaterial::default);
return OcclusionResult {
is_occluded: true,
transmission_factor: material.transmission,
high_freq_attenuation: material.high_freq_absorption,
low_freq_attenuation: material.low_freq_absorption,
diffraction_paths: Vec::new(),
};
}
}
OcclusionResult {
is_occluded: false,
transmission_factor: 1.0,
high_freq_attenuation: 1.0,
low_freq_attenuation: 1.0,
diffraction_paths: Vec::new(),
}
}
fn ray_casting_check(&self, source: Position3D, listener: Position3D) -> OcclusionResult {
self.line_of_sight_check(source, listener)
}
fn fresnel_zone_check(&self, source: Position3D, listener: Position3D) -> OcclusionResult {
self.line_of_sight_check(source, listener)
}
fn diffraction_check(&self, source: Position3D, listener: Position3D) -> OcclusionResult {
let mut result = self.line_of_sight_check(source, listener);
if result.is_occluded {
result.diffraction_paths = self.find_diffraction_paths(source, listener);
if !result.diffraction_paths.is_empty() {
result.transmission_factor = result.transmission_factor.max(0.1);
}
}
result
}
pub fn line_intersects_box(&self, start: Position3D, end: Position3D, box3d: &Box3D) -> bool {
let dir = Position3D::new(end.x - start.x, end.y - start.y, end.z - start.z);
let length = (dir.x * dir.x + dir.y * dir.y + dir.z * dir.z).sqrt();
if length == 0.0 {
return false;
}
let dir_norm = Position3D::new(dir.x / length, dir.y / length, dir.z / length);
let inv_dir = Position3D::new(
if dir_norm.x != 0.0 {
1.0 / dir_norm.x
} else {
f32::INFINITY
},
if dir_norm.y != 0.0 {
1.0 / dir_norm.y
} else {
f32::INFINITY
},
if dir_norm.z != 0.0 {
1.0 / dir_norm.z
} else {
f32::INFINITY
},
);
let t1 = (box3d.min.x - start.x) * inv_dir.x;
let t2 = (box3d.max.x - start.x) * inv_dir.x;
let t3 = (box3d.min.y - start.y) * inv_dir.y;
let t4 = (box3d.max.y - start.y) * inv_dir.y;
let t5 = (box3d.min.z - start.z) * inv_dir.z;
let t6 = (box3d.max.z - start.z) * inv_dir.z;
let tmin = t1.min(t2).max(t3.min(t4)).max(t5.min(t6));
let tmax = t1.max(t2).min(t3.max(t4)).min(t5.max(t6));
tmax >= 0.0 && tmin <= tmax && tmin <= length
}
fn find_diffraction_paths(
&self,
_source: Position3D,
_listener: Position3D,
) -> Vec<DiffractionPath> {
Vec::new()
}
}
impl Default for OcclusionDetector {
fn default() -> Self {
Self::new()
}
}
impl Default for OcclusionMaterial {
fn default() -> Self {
Self {
name: "Default".to_string(),
transmission: 0.1,
high_freq_absorption: 0.8,
low_freq_absorption: 0.3,
scattering: 0.2,
}
}
}