use crate::synthesis::simd::{SimdLanes, SimdWidth, SIMD};
use std::f32::consts::PI;
use wide::{f32x4, f32x8};
#[inline]
fn fast_inv_sqrt(x: f32) -> f32 {
let i = x.to_bits();
let i = 0x5f3759df - (i >> 1); let y = f32::from_bits(i);
let y = y * (1.5 - 0.5 * x * y * y);
y * (1.5 - 0.5 * x * y * y)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vec3 {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn zero() -> Self {
Self::new(0.0, 0.0, 0.0)
}
pub fn forward() -> Self {
Self::new(0.0, 0.0, 1.0)
}
pub fn up() -> Self {
Self::new(0.0, 1.0, 0.0)
}
pub fn right() -> Self {
Self::new(1.0, 0.0, 0.0)
}
pub fn length(&self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn length_squared(&self) -> f32 {
self.x * self.x + self.y * self.y + self.z * self.z
}
pub fn normalize(&self) -> Self {
let len_squared = self.length_squared();
if len_squared > 1e-10 {
let inv_len = fast_inv_sqrt(len_squared);
Self::new(self.x * inv_len, self.y * inv_len, self.z * inv_len)
} else {
*self
}
}
pub fn normalize_precise(&self) -> Self {
let len = self.length();
if len > 0.0 {
Self::new(self.x / len, self.y / len, self.z / len)
} else {
*self
}
}
pub fn dot(&self, other: &Vec3) -> f32 {
self.x * other.x + self.y * other.y + self.z * other.z
}
pub fn cross(&self, other: &Vec3) -> Self {
Self::new(
self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x,
)
}
pub fn sub(&self, other: &Vec3) -> Self {
Self::new(self.x - other.x, self.y - other.y, self.z - other.z)
}
pub fn add(&self, other: &Vec3) -> Self {
Self::new(self.x + other.x, self.y + other.y, self.z + other.z)
}
pub fn scale(&self, scalar: f32) -> Self {
Self::new(self.x * scalar, self.y * scalar, self.z * scalar)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AttenuationModel {
None,
Linear,
Inverse,
InverseSquare,
Exponential,
}
impl Default for AttenuationModel {
fn default() -> Self {
Self::InverseSquare
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpatialPosition {
pub position: Vec3,
pub velocity: Vec3,
}
impl SpatialPosition {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self {
position: Vec3::new(x, y, z),
velocity: Vec3::zero(),
}
}
pub fn with_velocity(x: f32, y: f32, z: f32, vx: f32, vy: f32, vz: f32) -> Self {
Self {
position: Vec3::new(x, y, z),
velocity: Vec3::new(vx, vy, vz),
}
}
pub fn set_position(&mut self, x: f32, y: f32, z: f32) {
self.position = Vec3::new(x, y, z);
}
pub fn set_velocity(&mut self, vx: f32, vy: f32, vz: f32) {
self.velocity = Vec3::new(vx, vy, vz);
}
}
impl Default for SpatialPosition {
fn default() -> Self {
Self::new(0.0, 0.0, 0.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SoundCone {
pub direction: Vec3,
pub inner_angle: f32,
pub outer_angle: f32,
pub outer_gain: f32,
}
impl SoundCone {
pub fn new(direction: Vec3, inner_angle: f32, outer_angle: f32, outer_gain: f32) -> Self {
Self {
direction: direction.normalize(),
inner_angle: inner_angle.clamp(0.0, 360.0),
outer_angle: outer_angle.clamp(0.0, 360.0),
outer_gain: outer_gain.clamp(0.0, 1.0),
}
}
pub fn narrow() -> Self {
Self::new(Vec3::forward(), 20.0, 40.0, 0.2)
}
pub fn medium() -> Self {
Self::new(Vec3::forward(), 45.0, 90.0, 0.3)
}
pub fn wide() -> Self {
Self::new(Vec3::forward(), 90.0, 150.0, 0.5)
}
pub fn cone_45() -> Self {
Self::new(Vec3::forward(), 20.0, 45.0, 0.2)
}
pub fn cone_90() -> Self {
Self::new(Vec3::forward(), 45.0, 90.0, 0.3)
}
pub fn cone_180() -> Self {
Self::new(Vec3::forward(), 90.0, 180.0, 0.5)
}
pub fn with_direction(mut self, x: f32, y: f32, z: f32) -> Self {
self.direction = Vec3::new(x, y, z).normalize();
self
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ListenerConfig {
pub position: Vec3,
pub forward: Vec3,
pub up: Vec3,
pub velocity: Vec3,
}
impl ListenerConfig {
pub fn new() -> Self {
Self {
position: Vec3::zero(),
forward: Vec3::forward(),
up: Vec3::up(),
velocity: Vec3::zero(),
}
}
pub fn with_position(mut self, x: f32, y: f32, z: f32) -> Self {
self.position = Vec3::new(x, y, z);
self
}
pub fn with_forward(mut self, x: f32, y: f32, z: f32) -> Self {
self.forward = Vec3::new(x, y, z).normalize();
self
}
pub fn with_up(mut self, x: f32, y: f32, z: f32) -> Self {
self.up = Vec3::new(x, y, z).normalize();
self
}
pub fn with_velocity(mut self, vx: f32, vy: f32, vz: f32) -> Self {
self.velocity = Vec3::new(vx, vy, vz);
self
}
pub fn right(&self) -> Vec3 {
self.forward.cross(&self.up).normalize()
}
}
impl Default for ListenerConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct SpatialParams {
pub attenuation_model: AttenuationModel,
pub ref_distance: f32,
pub max_distance: f32,
pub rolloff: f32,
pub speed_of_sound: f32,
pub doppler_enabled: bool,
pub doppler_factor: f32,
}
impl Default for SpatialParams {
fn default() -> Self {
Self {
attenuation_model: AttenuationModel::InverseSquare,
ref_distance: 1.0,
max_distance: 100.0,
rolloff: 1.0,
speed_of_sound: 343.0, doppler_enabled: true,
doppler_factor: 1.0,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SpatialResult {
pub volume: f32,
pub pan: f32,
pub pitch: f32,
pub occlusion: f32,
}
impl Default for SpatialResult {
fn default() -> Self {
Self {
volume: 1.0,
pan: 0.0,
pitch: 1.0,
occlusion: 0.0,
}
}
}
pub fn calculate_attenuation(
distance: f32,
model: AttenuationModel,
ref_distance: f32,
max_distance: f32,
rolloff: f32,
) -> f32 {
match model {
AttenuationModel::None => 1.0,
AttenuationModel::Linear => {
if distance >= max_distance {
0.0
} else {
1.0 - (distance / max_distance)
}
}
AttenuationModel::Inverse => {
let clamped_distance = distance.max(ref_distance);
ref_distance / (ref_distance + rolloff * (clamped_distance - ref_distance))
}
AttenuationModel::InverseSquare => {
if distance < ref_distance {
1.0
} else {
(ref_distance / distance).powi(2)
}
}
AttenuationModel::Exponential => {
if distance < ref_distance {
1.0
} else {
(distance / ref_distance).powf(-rolloff)
}
}
}
}
pub fn calculate_azimuth(source_pos: &Vec3, listener: &ListenerConfig) -> f32 {
let to_source = source_pos.sub(&listener.position);
let to_source_flat = Vec3::new(to_source.x, 0.0, to_source.z);
let forward_flat = Vec3::new(listener.forward.x, 0.0, listener.forward.z);
if to_source_flat.length() < 0.001 {
return 0.0; }
let to_source_norm = to_source_flat.normalize();
let forward_norm = forward_flat.normalize();
let dot = forward_norm.dot(&to_source_norm);
let cross = forward_norm.cross(&to_source_norm);
f32::fast_atan2(cross.y, dot)
}
pub fn azimuth_to_pan(azimuth: f32) -> f32 {
let clamped = azimuth.clamp(-PI / 2.0, PI / 2.0);
clamped / (PI / 2.0)
}
pub fn calculate_elevation(source_pos: &Vec3, listener: &ListenerConfig) -> f32 {
let to_source = source_pos.sub(&listener.position);
let distance_horizontal = (to_source.x * to_source.x + to_source.z * to_source.z).sqrt();
if distance_horizontal < 0.001 {
if to_source.y > 0.0 {
return PI / 2.0;
} else if to_source.y < 0.0 {
return -PI / 2.0;
}
return 0.0;
}
to_source.y.atan2(distance_horizontal)
}
pub fn calculate_elevation_effect(elevation: f32) -> (f32, f32) {
let abs_elevation = elevation.abs();
let elevation_factor = (abs_elevation / (PI / 2.0)).min(1.0);
let volume_attenuation = 1.0 - (elevation_factor * 0.5);
let pan_reduction = elevation_factor * 0.4;
(volume_attenuation, pan_reduction)
}
pub fn calculate_cone_gain(
source_pos: &Vec3,
listener_pos: &Vec3,
cone: &SoundCone,
) -> f32 {
let to_listener = listener_pos.sub(source_pos);
let distance = to_listener.length();
if distance < 0.001 {
return 1.0; }
let direction_to_listener = to_listener.scale(1.0 / distance);
let dot = cone.direction.dot(&direction_to_listener);
let angle_rad = dot.clamp(-1.0, 1.0).acos();
let angle_deg = angle_rad.to_degrees();
if angle_deg <= cone.inner_angle {
1.0
} else if angle_deg >= cone.outer_angle {
cone.outer_gain
} else {
let transition = (angle_deg - cone.inner_angle) / (cone.outer_angle - cone.inner_angle);
1.0 + (cone.outer_gain - 1.0) * transition
}
}
pub fn calculate_doppler(
source_pos: &Vec3,
source_velocity: &Vec3,
listener: &ListenerConfig,
speed_of_sound: f32,
doppler_factor: f32,
) -> f32 {
let to_source = source_pos.sub(&listener.position);
let distance = to_source.length();
if distance < 0.001 {
return 1.0; }
let direction = to_source.scale(1.0 / distance);
let source_radial_vel = source_velocity.dot(&direction);
let listener_radial_vel = listener.velocity.dot(&direction);
let relative_velocity = source_radial_vel - listener_radial_vel;
let doppler_shift = (speed_of_sound - relative_velocity * doppler_factor) / speed_of_sound;
doppler_shift.clamp(0.5, 2.0)
}
pub fn calculate_spatial(
source: &SpatialPosition,
listener: &ListenerConfig,
params: &SpatialParams,
) -> SpatialResult {
calculate_spatial_with_cone(source, listener, params, None, 0.0)
}
pub fn calculate_spatial_with_cone(
source: &SpatialPosition,
listener: &ListenerConfig,
params: &SpatialParams,
cone: Option<&SoundCone>,
occlusion: f32,
) -> SpatialResult {
let to_source = source.position.sub(&listener.position);
let distance_squared = to_source.length_squared();
let max_distance_squared = params.max_distance * params.max_distance;
if distance_squared >= max_distance_squared {
return SpatialResult {
volume: 0.0,
pan: 0.0,
pitch: 1.0,
occlusion: occlusion.clamp(0.0, 1.0),
};
}
let distance = distance_squared.sqrt();
let mut volume = calculate_attenuation(
distance,
params.attenuation_model,
params.ref_distance,
params.max_distance,
params.rolloff,
);
let azimuth = calculate_azimuth(&source.position, listener);
let mut pan = azimuth_to_pan(azimuth);
let elevation = calculate_elevation(&source.position, listener);
let (elevation_volume, pan_reduction) = calculate_elevation_effect(elevation);
volume *= elevation_volume;
pan *= 1.0 - pan_reduction;
if let Some(sound_cone) = cone {
let cone_gain = calculate_cone_gain(&source.position, &listener.position, sound_cone);
volume *= cone_gain;
}
let pitch = if params.doppler_enabled {
calculate_doppler(
&source.position,
&source.velocity,
listener,
params.speed_of_sound,
params.doppler_factor,
)
} else {
1.0
};
SpatialResult {
volume,
pan,
pitch,
occlusion: occlusion.clamp(0.0, 1.0),
}
}
#[inline]
fn batch_distance_squared_simd<V: SimdLanes>(
sources_x: &[f32],
sources_y: &[f32],
sources_z: &[f32],
listener_pos: &Vec3,
distances_squared_out: &mut [f32],
) {
let lanes = V::LANES;
let chunks = sources_x.len() / lanes;
let listener_x = V::splat(listener_pos.x);
let listener_y = V::splat(listener_pos.y);
let listener_z = V::splat(listener_pos.z);
for chunk_idx in 0..chunks {
let offset = chunk_idx * lanes;
let src_x = V::from_array(&sources_x[offset..offset + lanes]);
let src_y = V::from_array(&sources_y[offset..offset + lanes]);
let src_z = V::from_array(&sources_z[offset..offset + lanes]);
let dx = src_x.sub(listener_x);
let dy = src_y.sub(listener_y);
let dz = src_z.sub(listener_z);
let dx_sq = dx.mul(dx);
let dy_sq = dy.mul(dy);
let dz_sq = dz.mul(dz);
let dist_sq = dx_sq.add(dy_sq).add(dz_sq);
dist_sq.write_to_slice(&mut distances_squared_out[offset..offset + lanes]);
}
let remainder_start = chunks * lanes;
for i in remainder_start..sources_x.len() {
let dx = sources_x[i] - listener_pos.x;
let dy = sources_y[i] - listener_pos.y;
let dz = sources_z[i] - listener_pos.z;
distances_squared_out[i] = dx * dx + dy * dy + dz * dz;
}
}
pub fn batch_distance_squared(
sources_x: &[f32],
sources_y: &[f32],
sources_z: &[f32],
listener_pos: &Vec3,
distances_squared_out: &mut [f32],
) {
assert_eq!(sources_x.len(), sources_y.len());
assert_eq!(sources_x.len(), sources_z.len());
assert_eq!(sources_x.len(), distances_squared_out.len());
match SIMD.simd_width() {
SimdWidth::X8 => batch_distance_squared_simd::<f32x8>(
sources_x,
sources_y,
sources_z,
listener_pos,
distances_squared_out,
),
SimdWidth::X4 => batch_distance_squared_simd::<f32x4>(
sources_x,
sources_y,
sources_z,
listener_pos,
distances_squared_out,
),
SimdWidth::Scalar => {
for i in 0..sources_x.len() {
let dx = sources_x[i] - listener_pos.x;
let dy = sources_y[i] - listener_pos.y;
let dz = sources_z[i] - listener_pos.z;
distances_squared_out[i] = dx * dx + dy * dy + dz * dz;
}
}
}
}
#[inline]
fn batch_attenuation_simd<V: SimdLanes>(
distances: &[f32],
ref_distance: f32,
attenuations_out: &mut [f32],
) {
let lanes = V::LANES;
let chunks = distances.len() / lanes;
let ref_dist_sq = V::splat(ref_distance * ref_distance);
for chunk_idx in 0..chunks {
let offset = chunk_idx * lanes;
let dist_vec = V::from_array(&distances[offset..offset + lanes]);
let dist_sq = dist_vec.mul(dist_vec);
let attenuation = ref_dist_sq.div(dist_sq).min(V::splat(1.0));
attenuation.write_to_slice(&mut attenuations_out[offset..offset + lanes]);
}
let remainder_start = chunks * lanes;
for i in remainder_start..distances.len() {
let dist = distances[i];
if dist < ref_distance {
attenuations_out[i] = 1.0;
} else {
attenuations_out[i] = (ref_distance / dist).powi(2);
}
}
}
pub fn batch_attenuation_inverse_square(
distances: &[f32],
ref_distance: f32,
attenuations_out: &mut [f32],
) {
assert_eq!(distances.len(), attenuations_out.len());
match SIMD.simd_width() {
SimdWidth::X8 => batch_attenuation_simd::<f32x8>(distances, ref_distance, attenuations_out),
SimdWidth::X4 => batch_attenuation_simd::<f32x4>(distances, ref_distance, attenuations_out),
SimdWidth::Scalar => {
for i in 0..distances.len() {
let dist = distances[i];
if dist < ref_distance {
attenuations_out[i] = 1.0;
} else {
attenuations_out[i] = (ref_distance / dist).powi(2);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vec3_operations() {
let v1 = Vec3::new(1.0, 2.0, 3.0);
let v2 = Vec3::new(4.0, 5.0, 6.0);
assert_eq!(v1.add(&v2), Vec3::new(5.0, 7.0, 9.0));
assert_eq!(v1.sub(&v2), Vec3::new(-3.0, -3.0, -3.0));
assert_eq!(v1.scale(2.0), Vec3::new(2.0, 4.0, 6.0));
assert_eq!(v1.dot(&v2), 32.0);
}
#[test]
fn test_vec3_length() {
let v = Vec3::new(3.0, 4.0, 0.0);
assert!((v.length() - 5.0).abs() < 0.001);
assert_eq!(v.length_squared(), 25.0);
}
#[test]
fn test_vec3_normalize() {
let v = Vec3::new(3.0, 4.0, 0.0);
let normalized = v.normalize();
assert!((normalized.length() - 1.0).abs() < 0.001);
assert!((normalized.x - 0.6).abs() < 0.001);
assert!((normalized.y - 0.8).abs() < 0.001);
}
#[test]
fn test_attenuation_none() {
let attenuation = calculate_attenuation(10.0, AttenuationModel::None, 1.0, 100.0, 1.0);
assert_eq!(attenuation, 1.0);
}
#[test]
fn test_attenuation_linear() {
let attenuation = calculate_attenuation(50.0, AttenuationModel::Linear, 1.0, 100.0, 1.0);
assert!((attenuation - 0.5).abs() < 0.001);
}
#[test]
fn test_attenuation_inverse_square() {
let attenuation =
calculate_attenuation(2.0, AttenuationModel::InverseSquare, 1.0, 100.0, 1.0);
assert!((attenuation - 0.25).abs() < 0.001);
}
#[test]
fn test_azimuth_forward() {
let listener = ListenerConfig::new();
let source = Vec3::new(0.0, 0.0, 10.0); let azimuth = calculate_azimuth(&source, &listener);
assert!(azimuth.abs() < 0.01);
}
#[test]
fn test_azimuth_right() {
let listener = ListenerConfig::new();
let source = Vec3::new(10.0, 0.0, 0.0); let azimuth = calculate_azimuth(&source, &listener);
assert!((azimuth - PI / 2.0).abs() < 0.01);
}
#[test]
fn test_azimuth_left() {
let listener = ListenerConfig::new();
let source = Vec3::new(-10.0, 0.0, 0.0); let azimuth = calculate_azimuth(&source, &listener);
assert!((azimuth + PI / 2.0).abs() < 0.01);
}
#[test]
fn test_azimuth_to_pan() {
assert_eq!(azimuth_to_pan(0.0), 0.0); assert!((azimuth_to_pan(PI / 2.0) - 1.0).abs() < 0.001); assert!((azimuth_to_pan(-PI / 2.0) + 1.0).abs() < 0.001); }
#[test]
fn test_doppler_approaching() {
let source_pos = Vec3::new(0.0, 0.0, 10.0);
let source_velocity = Vec3::new(0.0, 0.0, -10.0); let listener = ListenerConfig::new();
let pitch = calculate_doppler(&source_pos, &source_velocity, &listener, 343.0, 1.0);
assert!(pitch > 1.0); }
#[test]
fn test_doppler_receding() {
let source_pos = Vec3::new(0.0, 0.0, 10.0);
let source_velocity = Vec3::new(0.0, 0.0, 10.0); let listener = ListenerConfig::new();
let pitch = calculate_doppler(&source_pos, &source_velocity, &listener, 343.0, 1.0);
assert!(pitch < 1.0); }
#[test]
fn test_spatial_calculation() {
let source = SpatialPosition::new(10.0, 0.0, 0.0); let listener = ListenerConfig::new();
let params = SpatialParams::default();
let result = calculate_spatial(&source, &listener, ¶ms);
assert!(result.volume < 1.0); assert!(result.pan > 0.0); assert!((result.pitch - 1.0).abs() < 0.01); }
#[test]
fn test_spatial_at_listener() {
let source = SpatialPosition::new(0.0, 0.0, 0.0); let listener = ListenerConfig::new();
let params = SpatialParams::default();
let result = calculate_spatial(&source, &listener, ¶ms);
assert_eq!(result.volume, 1.0); assert_eq!(result.pan, 0.0); }
#[test]
fn test_spatial_beyond_max_distance() {
let source = SpatialPosition::new(200.0, 0.0, 0.0); let listener = ListenerConfig::new();
let params = SpatialParams::default();
let result = calculate_spatial(&source, &listener, ¶ms);
assert_eq!(result.volume, 0.0); }
#[test]
fn test_elevation_above() {
let source = Vec3::new(0.0, 10.0, 0.0); let listener = ListenerConfig::new();
let elevation = calculate_elevation(&source, &listener);
assert!((elevation - PI / 2.0).abs() < 0.01);
}
#[test]
fn test_elevation_below() {
let source = Vec3::new(0.0, -10.0, 0.0); let listener = ListenerConfig::new();
let elevation = calculate_elevation(&source, &listener);
assert!((elevation + PI / 2.0).abs() < 0.01);
}
#[test]
fn test_elevation_ear_level() {
let source = Vec3::new(5.0, 0.0, 5.0); let listener = ListenerConfig::new();
let elevation = calculate_elevation(&source, &listener);
assert!(elevation.abs() < 0.01);
}
#[test]
fn test_elevation_effect_attenuation() {
let (volume, _) = calculate_elevation_effect(PI / 2.0);
assert!(volume < 1.0);
assert!(volume >= 0.5);
}
#[test]
fn test_elevation_effect_pan_reduction() {
let (_, pan_reduction) = calculate_elevation_effect(PI / 4.0);
assert!(pan_reduction > 0.0);
assert!(pan_reduction < 1.0);
}
#[test]
fn test_sound_cone_inside() {
let source_pos = Vec3::new(0.0, 0.0, 0.0);
let listener_pos = Vec3::new(0.0, 0.0, 5.0); let cone = SoundCone::new(Vec3::forward(), 60.0, 120.0, 0.3);
let gain = calculate_cone_gain(&source_pos, &listener_pos, &cone);
assert_eq!(gain, 1.0); }
#[test]
fn test_sound_cone_outside() {
let source_pos = Vec3::new(0.0, 0.0, 0.0);
let listener_pos = Vec3::new(0.0, 0.0, -5.0); let cone = SoundCone::new(Vec3::forward(), 60.0, 120.0, 0.3);
let gain = calculate_cone_gain(&source_pos, &listener_pos, &cone);
assert_eq!(gain, 0.3); }
#[test]
fn test_sound_cone_transition() {
let source_pos = Vec3::new(0.0, 0.0, 0.0);
let listener_pos = Vec3::new(5.0, 0.0, 5.0); let cone = SoundCone::new(Vec3::forward(), 30.0, 90.0, 0.3);
let gain = calculate_cone_gain(&source_pos, &listener_pos, &cone);
assert!(gain > 0.3); assert!(gain < 1.0); }
#[test]
fn test_spatial_with_cone() {
let source = SpatialPosition::new(0.0, 0.0, 5.0);
let listener = ListenerConfig::new();
let params = SpatialParams::default();
let cone = SoundCone::narrow();
let result = calculate_spatial_with_cone(&source, &listener, ¶ms, Some(&cone), 0.0);
assert!(result.volume > 0.0);
assert_eq!(result.occlusion, 0.0);
}
#[test]
fn test_occlusion() {
let source = SpatialPosition::new(5.0, 0.0, 0.0);
let listener = ListenerConfig::new();
let params = SpatialParams::default();
let result = calculate_spatial_with_cone(&source, &listener, ¶ms, None, 0.7);
assert_eq!(result.occlusion, 0.7);
}
#[test]
fn test_occlusion_clamping() {
let source = SpatialPosition::new(5.0, 0.0, 0.0);
let listener = ListenerConfig::new();
let params = SpatialParams::default();
let result = calculate_spatial_with_cone(&source, &listener, ¶ms, None, 1.5);
assert_eq!(result.occlusion, 1.0); }
#[test]
fn test_batch_distance_squared() {
let sources_x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let sources_y = vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
let sources_z = vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
let listener = Vec3::zero();
let mut distances_sq = vec![0.0; 8];
batch_distance_squared(&sources_x, &sources_y, &sources_z, &listener, &mut distances_sq);
for i in 0..8 {
let expected = (i + 1) as f32 * (i + 1) as f32;
assert!((distances_sq[i] - expected).abs() < 0.001);
}
}
#[test]
fn test_batch_distance_squared_3d() {
let sources_x = vec![3.0, 3.0, 3.0, 3.0];
let sources_y = vec![4.0, 4.0, 4.0, 4.0];
let sources_z = vec![0.0, 0.0, 0.0, 0.0];
let listener = Vec3::zero();
let mut distances_sq = vec![0.0; 4];
batch_distance_squared(&sources_x, &sources_y, &sources_z, &listener, &mut distances_sq);
for dist_sq in distances_sq.iter() {
assert!((*dist_sq - 25.0).abs() < 0.001); }
}
#[test]
fn test_batch_attenuation() {
let distances = vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0];
let ref_distance = 1.0;
let mut attenuations = vec![0.0; 8];
batch_attenuation_inverse_square(&distances, ref_distance, &mut attenuations);
assert!((attenuations[0] - 1.0).abs() < 0.001); assert!((attenuations[1] - 0.25).abs() < 0.001); assert!((attenuations[2] - 0.0625).abs() < 0.001); assert!((attenuations[3] - 0.015625).abs() < 0.001); }
#[test]
fn test_batch_attenuation_near_reference() {
let distances = vec![0.5, 0.75, 1.0, 1.5];
let ref_distance = 1.0;
let mut attenuations = vec![0.0; 4];
batch_attenuation_inverse_square(&distances, ref_distance, &mut attenuations);
assert_eq!(attenuations[0], 1.0); assert_eq!(attenuations[1], 1.0); assert_eq!(attenuations[2], 1.0); assert!((attenuations[3] - 0.444).abs() < 0.01); }
}