pub use crate::callback::DistanceAttenuationCallback;
use crate::context::Context;
use crate::geometry;
#[derive(Debug, Default)]
pub enum DistanceAttenuationModel {
#[default]
Default,
InverseDistance {
min_distance: f32,
},
Callback {
callback: DistanceAttenuationCallback,
dirty: bool,
},
}
impl From<&DistanceAttenuationModel> for audionimbus_sys::IPLDistanceAttenuationModel {
fn from(distance_attenuation_model: &DistanceAttenuationModel) -> Self {
let (type_, min_distance, callback, user_data, dirty) = match distance_attenuation_model {
DistanceAttenuationModel::Default => (
audionimbus_sys::IPLDistanceAttenuationModelType::IPL_DISTANCEATTENUATIONTYPE_DEFAULT,
f32::default(),
None,
std::ptr::null_mut(),
bool::default()
),
DistanceAttenuationModel::InverseDistance { min_distance } => (
audionimbus_sys::IPLDistanceAttenuationModelType::IPL_DISTANCEATTENUATIONTYPE_INVERSEDISTANCE,
*min_distance,
None,
std::ptr::null_mut(),
bool::default()
),
DistanceAttenuationModel::Callback { callback, dirty } => {
let (callback_fn, user_data) = callback.as_raw_parts();
(
audionimbus_sys::IPLDistanceAttenuationModelType::IPL_DISTANCEATTENUATIONTYPE_CALLBACK,
f32::default(),
Some(callback_fn),
user_data,
*dirty
)
}
};
Self {
type_,
minDistance: min_distance,
callback,
userData: user_data,
dirty: if dirty {
audionimbus_sys::IPLbool::IPL_TRUE
} else {
audionimbus_sys::IPLbool::IPL_FALSE
},
}
}
}
pub fn distance_attenuation(
context: &Context,
source: geometry::Point,
listener: geometry::Point,
model: &DistanceAttenuationModel,
) -> f32 {
unsafe {
audionimbus_sys::iplDistanceAttenuationCalculate(
context.raw_ptr(),
source.into(),
listener.into(),
&mut model.into(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Point;
#[test]
fn test_default_model() {
let context = Context::default();
let source = Point::new(10.0, 0.0, 0.0);
let listener = Point::new(0.0, 0.0, 0.0);
let model = DistanceAttenuationModel::default();
let attenuation = distance_attenuation(&context, source, listener, &model);
assert_eq!(attenuation, 0.1);
}
#[test]
fn test_inverse_distance_model() {
let context = Context::default();
let source = Point::new(5.0, 0.0, 0.0);
let listener = Point::new(0.0, 0.0, 0.0);
let model = DistanceAttenuationModel::InverseDistance { min_distance: 1.0 };
let attenuation = distance_attenuation(&context, source, listener, &model);
assert_eq!(attenuation, 0.2);
}
#[test]
fn test_zero_distance() {
let context = Context::default();
let source = Point::new(0.0, 0.0, 0.0);
let listener = Point::new(0.0, 0.0, 0.0);
let model = DistanceAttenuationModel::default();
let attenuation = distance_attenuation(&context, source, listener, &model);
assert_eq!(attenuation, 1.0);
}
#[test]
fn test_various_distances() {
let context = Context::default();
let listener = Point::new(0.0, 0.0, 0.0);
let model = DistanceAttenuationModel::default();
let distances = [1.0, 5.0, 10.0, 50.0];
let mut prev_attenuation = 1.1;
for &dist in &distances {
let source = Point::new(dist, 0.0, 0.0);
let attenuation = distance_attenuation(&context, source, listener, &model);
assert!(attenuation < prev_attenuation);
prev_attenuation = attenuation;
}
}
#[test]
fn test_callback_model() {
let context = Context::default();
let source = Point::new(10.0, 0.0, 0.0);
let listener = Point::new(0.0, 0.0, 0.0);
let model = DistanceAttenuationModel::Callback {
callback: DistanceAttenuationCallback::new(|distance: f32| {
(1.0 - distance / 100.0).max(0.0)
}),
dirty: false,
};
let attenuation = distance_attenuation(&context, source, listener, &model);
assert_eq!(attenuation, 0.9);
}
}