use crate::baking::BakedDataIdentifier;
use crate::context::Context;
use crate::energy_field::EnergyField;
use crate::error::{to_option_error, SteamAudioError};
use crate::geometry::{Matrix, Scene, Sphere};
use crate::serialized_object::SerializedObject;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub struct ProbeArray(audionimbus_sys::IPLProbeArray);
impl ProbeArray {
pub fn try_new(context: &Context) -> Result<Self, SteamAudioError> {
let mut probe_array = Self(std::ptr::null_mut());
let status = unsafe {
audionimbus_sys::iplProbeArrayCreate(context.raw_ptr(), probe_array.raw_ptr_mut())
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(probe_array)
}
pub fn generate_probes(&mut self, scene: &Scene, probe_params: &ProbeGenerationParams) {
unsafe {
audionimbus_sys::iplProbeArrayGenerateProbes(
self.raw_ptr(),
scene.raw_ptr(),
&mut audionimbus_sys::IPLProbeGenerationParams::from(*probe_params),
);
}
}
pub fn num_probes(&self) -> usize {
unsafe { audionimbus_sys::iplProbeArrayGetNumProbes(self.raw_ptr()) as usize }
}
pub fn probe(&self, index: usize) -> Result<Sphere, ProbeArrayError> {
let num_probes = self.num_probes();
if index >= num_probes {
return Err(ProbeArrayError::ProbeIndexOutOfBounds {
probe_index: index,
num_probes,
});
}
let ipl_sphere =
unsafe { audionimbus_sys::iplProbeArrayGetProbe(self.raw_ptr(), index as i32) };
Ok(Sphere::from(ipl_sphere))
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLProbeArray {
self.0
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLProbeArray {
&mut self.0
}
}
impl Drop for ProbeArray {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplProbeArrayRelease(&raw mut self.0) }
}
}
unsafe impl Send for ProbeArray {}
unsafe impl Sync for ProbeArray {}
impl Clone for ProbeArray {
fn clone(&self) -> Self {
Self(unsafe { audionimbus_sys::iplProbeArrayRetain(self.0) })
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ProbeArrayError {
ProbeIndexOutOfBounds {
probe_index: usize,
num_probes: usize,
},
}
impl std::error::Error for ProbeArrayError {}
impl std::fmt::Display for ProbeArrayError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::ProbeIndexOutOfBounds {
probe_index,
num_probes,
} => write!(
f,
"probe index {probe_index} out of bounds (num_probes: {num_probes})"
),
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum ProbeGenerationParams {
Centroid {
transform: Matrix<f32, 4, 4>,
},
UniformFloor {
spacing: f32,
height: f32,
transform: Matrix<f32, 4, 4>,
},
}
impl From<ProbeGenerationParams> for audionimbus_sys::IPLProbeGenerationParams {
fn from(probe_generation_params: ProbeGenerationParams) -> Self {
let (type_, spacing, height, transform) = match probe_generation_params {
ProbeGenerationParams::Centroid { transform } => (
audionimbus_sys::IPLProbeGenerationType::IPL_PROBEGENERATIONTYPE_CENTROID,
f32::default(),
f32::default(),
transform,
),
ProbeGenerationParams::UniformFloor {
spacing,
height,
transform,
} => (
audionimbus_sys::IPLProbeGenerationType::IPL_PROBEGENERATIONTYPE_UNIFORMFLOOR,
spacing,
height,
transform,
),
};
Self {
type_,
spacing,
height,
transform: transform.into(),
}
}
}
#[derive(Debug)]
pub struct ProbeBatch {
inner: audionimbus_sys::IPLProbeBatch,
shared: Arc<Mutex<ProbeBatchShared>>,
}
#[derive(Default, Debug)]
struct ProbeBatchShared {
committed_num_probes: usize,
pending_num_probes: i32,
}
impl ProbeBatch {
pub fn try_new(context: &Context) -> Result<Self, SteamAudioError> {
let mut probe_batch = Self {
inner: std::ptr::null_mut(),
shared: Arc::new(Mutex::new(ProbeBatchShared::default())),
};
let status = unsafe {
audionimbus_sys::iplProbeBatchCreate(context.raw_ptr(), probe_batch.raw_ptr_mut())
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(probe_batch)
}
pub fn num_probes(&self) -> usize {
unsafe { audionimbus_sys::iplProbeBatchGetNumProbes(self.raw_ptr()) as usize }
}
pub fn committed_num_probes(&self) -> usize {
self.shared.lock().unwrap().committed_num_probes
}
pub fn data_size(&self, identifier: BakedDataIdentifier) -> usize {
let mut ffi_identifier: audionimbus_sys::IPLBakedDataIdentifier = identifier.into();
unsafe {
audionimbus_sys::iplProbeBatchGetDataSize(self.raw_ptr(), &raw mut ffi_identifier)
as usize
}
}
pub fn remove_data(&mut self, identifier: BakedDataIdentifier) {
let mut ffi_identifier: audionimbus_sys::IPLBakedDataIdentifier = identifier.into();
unsafe {
audionimbus_sys::iplProbeBatchRemoveData(self.raw_ptr(), &raw mut ffi_identifier);
}
}
pub fn add_probe(&mut self, probe: Sphere) {
unsafe {
audionimbus_sys::iplProbeBatchAddProbe(
self.raw_ptr(),
audionimbus_sys::IPLSphere::from(probe),
);
}
self.shared.lock().unwrap().pending_num_probes += 1;
}
pub fn remove_probe(&mut self, probe_index: usize) -> Result<(), ProbeBatchError> {
let num_probes = self.num_probes();
if probe_index >= num_probes {
return Err(ProbeBatchError::ProbeIndexOutOfBounds {
probe_index,
num_probes,
});
}
unsafe {
audionimbus_sys::iplProbeBatchRemoveProbe(self.raw_ptr(), probe_index as i32);
}
self.shared.lock().unwrap().pending_num_probes -= 1;
Ok(())
}
pub fn add_probe_array(&mut self, probe_array: &ProbeArray) {
unsafe {
audionimbus_sys::iplProbeBatchAddProbeArray(self.raw_ptr(), probe_array.raw_ptr());
}
self.shared.lock().unwrap().pending_num_probes += probe_array.num_probes() as i32;
}
pub fn reverb(
&self,
identifier: BakedDataIdentifier,
probe_index: usize,
) -> Result<[f32; 3], ProbeBatchError> {
let num_probes = self.num_probes();
if probe_index >= num_probes {
return Err(ProbeBatchError::ProbeIndexOutOfBounds {
probe_index,
num_probes,
});
}
let mut ffi_identifier: audionimbus_sys::IPLBakedDataIdentifier = identifier.into();
let mut reverb_times: [f32; 3] = [0.0; 3];
unsafe {
audionimbus_sys::iplProbeBatchGetReverb(
self.raw_ptr(),
&raw mut ffi_identifier,
probe_index as i32,
reverb_times.as_mut_ptr(),
);
}
Ok(reverb_times)
}
pub fn energy_field(
&self,
identifier: BakedDataIdentifier,
probe_index: usize,
) -> Result<EnergyField, ProbeBatchError> {
let num_probes = self.num_probes();
if probe_index >= num_probes {
return Err(ProbeBatchError::ProbeIndexOutOfBounds {
probe_index,
num_probes,
});
}
let mut ffi_identifier: audionimbus_sys::IPLBakedDataIdentifier = identifier.into();
let energy_field = EnergyField(std::ptr::null_mut());
unsafe {
audionimbus_sys::iplProbeBatchGetEnergyField(
self.raw_ptr(),
&raw mut ffi_identifier,
probe_index as i32,
energy_field.raw_ptr(),
);
}
Ok(energy_field)
}
pub fn commit(&mut self) {
unsafe { audionimbus_sys::iplProbeBatchCommit(self.raw_ptr()) }
let mut shared = self.shared.lock().unwrap();
shared.committed_num_probes = shared
.committed_num_probes
.saturating_add_signed(shared.pending_num_probes as isize);
shared.pending_num_probes = 0;
}
pub fn save(&self, serialized_object: &mut SerializedObject) {
unsafe {
audionimbus_sys::iplProbeBatchSave(self.raw_ptr(), serialized_object.raw_ptr());
}
}
pub fn load(
context: &Context,
serialized_object: &mut SerializedObject,
) -> Result<Self, SteamAudioError> {
let shared = Arc::new(Mutex::new(ProbeBatchShared::default()));
let mut probe_batch = Self {
inner: std::ptr::null_mut(),
shared: shared.clone(),
};
let status = unsafe {
audionimbus_sys::iplProbeBatchLoad(
context.raw_ptr(),
serialized_object.raw_ptr(),
probe_batch.raw_ptr_mut(),
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
shared.lock().unwrap().committed_num_probes = probe_batch.num_probes();
Ok(probe_batch)
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLProbeBatch {
self.inner
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLProbeBatch {
&mut self.inner
}
}
impl Drop for ProbeBatch {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplProbeBatchRelease(&raw mut self.inner) }
}
}
unsafe impl Send for ProbeBatch {}
unsafe impl Sync for ProbeBatch {}
impl Clone for ProbeBatch {
fn clone(&self) -> Self {
Self {
inner: unsafe { audionimbus_sys::iplProbeBatchRetain(self.inner) },
shared: Arc::clone(&self.shared),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ProbeBatchError {
ProbeIndexOutOfBounds {
probe_index: usize,
num_probes: usize,
},
}
impl std::error::Error for ProbeBatchError {}
impl std::fmt::Display for ProbeBatchError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::ProbeIndexOutOfBounds {
probe_index,
num_probes,
} => write!(
f,
"probe index {probe_index} out of bounds (num_probes: {num_probes})"
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Material, Point, StaticMesh, StaticMeshSettings, Triangle};
mod probe_array {
use super::*;
#[test]
fn test_try_new() {
let context = Context::default();
let probe_array = ProbeArray::try_new(&context);
assert!(probe_array.is_ok());
}
#[test]
fn test_generation_centroid() {
let context = Context::default();
let scene = Scene::try_new(&context).expect("failed to create scene");
let mut probe_array = ProbeArray::try_new(&context).unwrap();
let transform = Matrix::new([
[10.0, 0.0, 0.0, 0.0],
[0.0, 10.0, 0.0, 0.0],
[0.0, 0.0, 10.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]);
let params = ProbeGenerationParams::Centroid { transform };
probe_array.generate_probes(&scene, ¶ms);
assert_eq!(probe_array.num_probes(), 1);
}
#[test]
fn test_generation_uniform_floor() {
let context = Context::default();
let mut scene = Scene::try_new(&context).expect("failed to create scene");
let vertices = vec![
Point::new(-50.0, 0.0, -50.0),
Point::new(50.0, 0.0, -50.0),
Point::new(50.0, 0.0, 50.0),
Point::new(-50.0, 0.0, 50.0),
];
let triangles = vec![Triangle::new(0, 1, 2), Triangle::new(0, 2, 3)];
let material_indices = vec![0, 0];
let materials = vec![Material::default()];
let static_mesh_settings = StaticMeshSettings {
vertices: &vertices,
triangles: &triangles,
material_indices: &material_indices,
materials: &materials,
};
let static_mesh = StaticMesh::try_new(&scene, &static_mesh_settings).unwrap();
scene.add_static_mesh(static_mesh);
scene.commit();
let mut probe_array = ProbeArray::try_new(&context).unwrap();
let transform = Matrix::new([
[100.0, 0.0, 0.0, 0.0],
[0.0, 100.0, 0.0, 0.0],
[0.0, 0.0, 100.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]);
let params = ProbeGenerationParams::UniformFloor {
spacing: 10.0,
height: 1.5,
transform,
};
probe_array.generate_probes(&scene, ¶ms);
assert_eq!(probe_array.num_probes(), 121);
}
#[test]
fn test_clone() {
let context = Context::default();
let probe_array = ProbeArray::try_new(&context).unwrap();
let clone = probe_array.clone();
assert_eq!(probe_array.raw_ptr(), clone.raw_ptr());
drop(probe_array);
assert!(!clone.raw_ptr().is_null());
}
}
mod probe_batch {
use super::*;
#[test]
fn test_try_new() {
let context = Context::default();
let probe_batch = ProbeBatch::try_new(&context);
assert!(probe_batch.is_ok());
}
#[test]
fn test_add_remove() {
let context = Context::default();
let mut probe_batch = ProbeBatch::try_new(&context).unwrap();
let probe = Sphere {
center: Point::new(0.0, 0.0, 0.0),
radius: 1.0,
};
probe_batch.add_probe(probe);
probe_batch.commit();
assert_eq!(probe_batch.num_probes(), 1);
probe_batch.remove_probe(0).unwrap();
probe_batch.commit();
assert_eq!(probe_batch.num_probes(), 0);
}
#[test]
fn test_add_array() {
let context = Context::default();
let scene = Scene::try_new(&context).expect("failed to create scene");
let mut probe_array = ProbeArray::try_new(&context).unwrap();
let transform = Matrix::new([
[10.0, 0.0, 0.0, 0.0],
[0.0, 10.0, 0.0, 0.0],
[0.0, 0.0, 10.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]);
let params = ProbeGenerationParams::Centroid { transform };
probe_array.generate_probes(&scene, ¶ms);
let mut probe_batch = ProbeBatch::try_new(&context).unwrap();
probe_batch.add_probe_array(&probe_array);
probe_batch.commit();
assert_eq!(probe_batch.num_probes(), probe_array.num_probes());
}
#[test]
fn test_remove_out_of_bounds() {
let context = Context::default();
let mut probe_batch = ProbeBatch::try_new(&context).unwrap();
assert_eq!(
probe_batch.remove_probe(2),
Err(ProbeBatchError::ProbeIndexOutOfBounds {
probe_index: 2,
num_probes: 0,
})
);
}
#[test]
fn test_clone() {
let context = Context::default();
let probe_batch = ProbeBatch::try_new(&context).unwrap();
let clone = probe_batch.clone();
assert_eq!(probe_batch.raw_ptr(), clone.raw_ptr());
drop(probe_batch);
assert!(!clone.raw_ptr().is_null());
}
}
}