use super::{InstancedMesh, Matrix, StaticMesh};
use crate::callback::{CustomRayTracingCallbacks, CustomRayTracingUserData, ProgressCallback};
use crate::context::Context;
use crate::device::embree::EmbreeDevice;
use crate::device::radeon_rays::RadeonRaysDevice;
use crate::error::{to_option_error, SteamAudioError};
use crate::geometry::{Direction, Point};
use crate::ray_tracing::{CustomRayTracer, DefaultRayTracer, Embree, RadeonRays, RayTracer};
use crate::serialized_object::SerializedObject;
use crate::Sealed;
use slotmap::{DefaultKey, SlotMap};
use std::marker::PhantomData;
use std::sync::{Arc, Mutex};
pub trait SaveableAsSerialized: Sealed {}
impl SaveableAsSerialized for DefaultRayTracer {}
pub trait SaveableAsObj: Sealed {}
impl SaveableAsObj for DefaultRayTracer {}
impl SaveableAsObj for Embree {}
#[derive(Debug)]
pub struct Scene<'a, T: RayTracer = DefaultRayTracer> {
inner: audionimbus_sys::IPLScene,
shared: Arc<Mutex<SceneShared<T>>>,
_ray_tracing_device: PhantomData<&'a ()>,
_marker: PhantomData<T>,
}
#[derive(Debug)]
struct SceneShared<T: RayTracer> {
static_meshes: SlotMap<DefaultKey, StaticMesh<T>>,
instanced_meshes: SlotMap<DefaultKey, InstancedMesh>,
static_meshes_to_remove: Vec<StaticMesh<T>>,
instanced_meshes_to_remove: Vec<InstancedMesh>,
_callback_user_data: Option<Box<CustomRayTracingUserData>>,
}
impl<T: RayTracer> Default for SceneShared<T> {
fn default() -> Self {
Self {
static_meshes: SlotMap::new(),
instanced_meshes: SlotMap::new(),
static_meshes_to_remove: Vec::new(),
instanced_meshes_to_remove: Vec::new(),
_callback_user_data: None,
}
}
}
impl<T: RayTracer> Scene<'_, T> {
fn empty() -> Self {
let shared = SceneShared {
static_meshes: SlotMap::new(),
instanced_meshes: SlotMap::new(),
static_meshes_to_remove: Vec::new(),
instanced_meshes_to_remove: Vec::new(),
_callback_user_data: None,
};
Self {
inner: std::ptr::null_mut(),
shared: Arc::new(Mutex::new(shared)),
_ray_tracing_device: PhantomData,
_marker: PhantomData,
}
}
fn from_ffi_create(
context: &Context,
settings: &mut audionimbus_sys::IPLSceneSettings,
callback_user_data: Option<Box<CustomRayTracingUserData>>,
) -> Result<Self, SteamAudioError> {
let mut scene = Self::empty();
scene.shared.lock().unwrap()._callback_user_data = callback_user_data;
let status = unsafe {
audionimbus_sys::iplSceneCreate(context.raw_ptr(), settings, scene.raw_ptr_mut())
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(scene)
}
fn from_ffi_load(
context: &Context,
settings: &mut audionimbus_sys::IPLSceneSettings,
callback_user_data: Option<Box<CustomRayTracingUserData>>,
serialized_object: &SerializedObject,
progress_callback: Option<ProgressCallback>,
) -> Result<Self, SteamAudioError> {
let mut scene = Self::empty();
scene.shared.lock().unwrap()._callback_user_data = callback_user_data;
let (callback_fn, user_data) =
progress_callback.map_or((None, std::ptr::null_mut()), |callback| {
let (callback_fn, user_data) = callback.as_raw_parts();
(Some(callback_fn), user_data)
});
let status = unsafe {
audionimbus_sys::iplSceneLoad(
context.raw_ptr(),
settings,
serialized_object.raw_ptr(),
callback_fn,
user_data,
scene.raw_ptr_mut(),
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(scene)
}
}
impl Scene<'_, DefaultRayTracer> {
pub fn try_new(context: &Context) -> Result<Self, SteamAudioError> {
Self::from_ffi_create(context, &mut Self::ffi_settings(), None)
}
pub fn load(
context: &Context,
serialized_object: &SerializedObject,
) -> Result<Self, SteamAudioError> {
Self::from_ffi_load(
context,
&mut Self::ffi_settings(),
None,
serialized_object,
None,
)
}
pub fn load_with_progress(
context: &Context,
serialized_object: &SerializedObject,
progress_callback: ProgressCallback,
) -> Result<Self, SteamAudioError> {
Self::from_ffi_load(
context,
&mut Self::ffi_settings(),
None,
serialized_object,
Some(progress_callback),
)
}
fn ffi_settings() -> audionimbus_sys::IPLSceneSettings {
audionimbus_sys::IPLSceneSettings {
type_: DefaultRayTracer::scene_type(),
closestHitCallback: None,
anyHitCallback: None,
batchedClosestHitCallback: None,
batchedAnyHitCallback: None,
userData: std::ptr::null_mut(),
embreeDevice: std::ptr::null_mut(),
radeonRaysDevice: std::ptr::null_mut(),
}
}
}
impl<'a> Scene<'a, Embree> {
pub fn try_with_embree(
context: &Context,
device: &'a EmbreeDevice,
) -> Result<Self, SteamAudioError> {
Self::from_ffi_create(context, &mut Self::ffi_settings(device), None)
}
pub fn load_embree(
context: &Context,
device: &'a EmbreeDevice,
serialized_object: &SerializedObject,
) -> Result<Self, SteamAudioError> {
let mut scene = Self {
inner: std::ptr::null_mut(),
#[allow(clippy::arc_with_non_send_sync)]
shared: Arc::new(Mutex::new(SceneShared::default())),
_ray_tracing_device: PhantomData,
_marker: PhantomData,
};
let status = unsafe {
audionimbus_sys::iplSceneLoad(
context.raw_ptr(),
&mut Self::ffi_settings(device),
serialized_object.raw_ptr(),
None,
std::ptr::null_mut(),
scene.raw_ptr_mut(),
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(scene)
}
pub fn load_embree_with_progress(
context: &Context,
device: &'a EmbreeDevice,
serialized_object: &SerializedObject,
progress_callback: ProgressCallback,
) -> Result<Self, SteamAudioError> {
Self::from_ffi_load(
context,
&mut Self::ffi_settings(device),
None,
serialized_object,
Some(progress_callback),
)
}
fn ffi_settings(device: &'a EmbreeDevice) -> audionimbus_sys::IPLSceneSettings {
audionimbus_sys::IPLSceneSettings {
type_: Embree::scene_type(),
closestHitCallback: None,
anyHitCallback: None,
batchedClosestHitCallback: None,
batchedAnyHitCallback: None,
userData: std::ptr::null_mut(),
embreeDevice: device.raw_ptr(),
radeonRaysDevice: std::ptr::null_mut(),
}
}
}
impl<'a> Scene<'a, RadeonRays> {
pub fn try_with_radeon_rays(
context: &Context,
device: &'a RadeonRaysDevice,
) -> Result<Self, SteamAudioError> {
Self::from_ffi_create(context, &mut Self::ffi_settings(device), None)
}
pub fn load_radeon_rays(
context: &Context,
device: &'a RadeonRaysDevice,
serialized_object: &SerializedObject,
) -> Result<Self, SteamAudioError> {
Self::from_ffi_load(
context,
&mut Self::ffi_settings(device),
None,
serialized_object,
None,
)
}
pub fn load_radeon_rays_with_progress(
context: &Context,
device: &'a RadeonRaysDevice,
serialized_object: &SerializedObject,
progress_callback: ProgressCallback,
) -> Result<Self, SteamAudioError> {
Self::from_ffi_load(
context,
&mut Self::ffi_settings(device),
None,
serialized_object,
Some(progress_callback),
)
}
fn ffi_settings(device: &'a RadeonRaysDevice) -> audionimbus_sys::IPLSceneSettings {
audionimbus_sys::IPLSceneSettings {
type_: RadeonRays::scene_type(),
closestHitCallback: None,
anyHitCallback: None,
batchedClosestHitCallback: None,
batchedAnyHitCallback: None,
userData: std::ptr::null_mut(),
embreeDevice: std::ptr::null_mut(),
radeonRaysDevice: device.raw_ptr(),
}
}
}
impl<'a> Scene<'a, CustomRayTracer> {
pub fn try_with_custom(
context: &Context,
callbacks: &'a CustomRayTracingCallbacks,
) -> Result<Self, SteamAudioError> {
let (mut settings, user_data) = callbacks.as_ffi_settings();
Self::from_ffi_create(context, &mut settings, Some(user_data))
}
pub fn load_custom(
context: &Context,
callbacks: &'a CustomRayTracingCallbacks,
serialized_object: &SerializedObject,
) -> Result<Self, SteamAudioError> {
let (mut settings, user_data) = callbacks.as_ffi_settings();
Self::from_ffi_load(
context,
&mut settings,
Some(user_data),
serialized_object,
None,
)
}
pub fn load_custom_with_progress(
context: &Context,
callbacks: &'a CustomRayTracingCallbacks,
serialized_object: &SerializedObject,
progress_callback: ProgressCallback,
) -> Result<Self, SteamAudioError> {
let (mut settings, user_data) = callbacks.as_ffi_settings();
Self::from_ffi_load(
context,
&mut settings,
Some(user_data),
serialized_object,
Some(progress_callback),
)
}
}
impl<T: RayTracer> Scene<'_, T> {
pub fn add_static_mesh(&mut self, static_mesh: StaticMesh<T>) -> StaticMeshHandle {
unsafe {
audionimbus_sys::iplStaticMeshAdd(static_mesh.raw_ptr(), self.raw_ptr());
}
let key = self
.shared
.lock()
.unwrap()
.static_meshes
.insert(static_mesh);
StaticMeshHandle(key)
}
pub fn remove_static_mesh(&mut self, handle: StaticMeshHandle) -> bool {
let mut shared = self.shared.lock().unwrap();
let Some(static_mesh) = shared.static_meshes.remove(handle.0) else {
return false;
};
unsafe {
audionimbus_sys::iplStaticMeshRemove(static_mesh.raw_ptr(), self.raw_ptr());
}
shared.static_meshes_to_remove.push(static_mesh);
true
}
pub fn add_instanced_mesh(&mut self, instanced_mesh: InstancedMesh) -> InstancedMeshHandle {
unsafe {
audionimbus_sys::iplInstancedMeshAdd(instanced_mesh.raw_ptr(), self.raw_ptr());
}
let mut shared = self.shared.lock().unwrap();
let key = shared.instanced_meshes.insert(instanced_mesh);
InstancedMeshHandle(key)
}
pub fn remove_instanced_mesh(&mut self, handle: InstancedMeshHandle) -> bool {
let mut shared = self.shared.lock().unwrap();
let Some(instanced_mesh) = shared.instanced_meshes.remove(handle.0) else {
return false;
};
unsafe {
audionimbus_sys::iplInstancedMeshRemove(instanced_mesh.raw_ptr(), self.raw_ptr());
}
shared.instanced_meshes_to_remove.push(instanced_mesh);
true
}
pub fn update_instanced_mesh_transform(
&mut self,
handle: InstancedMeshHandle,
transform: Matrix<f32, 4, 4>,
) -> bool {
let shared = self.shared.lock().unwrap();
let Some(instanced_mesh) = shared.instanced_meshes.get(handle.0) else {
return false;
};
unsafe {
audionimbus_sys::iplInstancedMeshUpdateTransform(
instanced_mesh.raw_ptr(),
self.raw_ptr(),
transform.into(),
);
}
true
}
pub fn commit(&mut self) {
unsafe {
audionimbus_sys::iplSceneCommit(self.raw_ptr());
}
let mut shared = self.shared.lock().unwrap();
shared.static_meshes_to_remove.clear();
shared.instanced_meshes_to_remove.clear();
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLScene {
self.inner
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLScene {
&mut self.inner
}
}
impl<T: RayTracer + SaveableAsSerialized> Scene<'_, T> {
pub fn save(&self) -> SerializedObject {
let serialized_object = SerializedObject(std::ptr::null_mut());
unsafe {
audionimbus_sys::iplSceneSave(self.raw_ptr(), serialized_object.raw_ptr());
}
serialized_object
}
}
impl<T: RayTracer + SaveableAsObj> Scene<'_, T> {
pub fn save_obj(&self, filename: String) {
let filename_c_string =
std::ffi::CString::new(filename).expect("failed to create a CString from the filename");
unsafe { audionimbus_sys::iplSceneSaveOBJ(self.raw_ptr(), filename_c_string.as_ptr()) }
}
}
impl<T: RayTracer> Drop for Scene<'_, T> {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplSceneRelease(&raw mut self.inner) }
}
}
unsafe impl<T: RayTracer> Send for Scene<'_, T> {}
unsafe impl<T: RayTracer> Sync for Scene<'_, T> {}
impl<T: RayTracer> Clone for Scene<'_, T> {
fn clone(&self) -> Self {
Self {
inner: unsafe { audionimbus_sys::iplSceneRetain(self.inner) },
shared: Arc::clone(&self.shared),
_ray_tracing_device: PhantomData,
_marker: PhantomData,
}
}
}
pub fn relative_direction(
context: &Context,
source_position: Point,
listener_position: Point,
listener_ahead: Direction,
listener_up: Direction,
) -> Direction {
let relative_direction = unsafe {
audionimbus_sys::iplCalculateRelativeDirection(
context.raw_ptr(),
source_position.into(),
listener_position.into(),
listener_ahead.into(),
listener_up.into(),
)
};
relative_direction.into()
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct StaticMeshHandle(DefaultKey);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct InstancedMeshHandle(DefaultKey);
#[cfg(test)]
mod tests {
use super::*;
use crate::{
AnyHitCallback, BatchedAnyHitCallback, BatchedClosestHitCallback, ClosestHitCallback,
CustomRayTracingCallbacks, Vector3,
};
#[test]
fn test_default_scene() {
let context = Context::default();
assert!(Scene::try_new(&context).is_ok());
}
#[test]
fn test_relative_direction() {
let context = Context::default();
let source_position = Point::new(1.0, 0.0, 0.0);
let listener_position = Point::new(0.0, 0.0, 0.0);
let listener_ahead = Direction::new(0.0, 0.0, 1.0);
let listener_up = Direction::new(0.0, 1.0, 0.0);
let direction = relative_direction(
&context,
source_position,
listener_position,
listener_ahead,
listener_up,
);
assert_eq!(
direction,
Vector3 {
x: -1.0,
y: 0.0,
z: 0.0
}
);
}
#[test]
fn test_custom_ray_tracer() {
let context = Context::default();
let closest_hit = ClosestHitCallback::new(|_ray, _min_dist, _max_dist| None);
let any_hit = AnyHitCallback::new(|_ray, _min_dist, _max_dist| false);
let batched_closest_hit =
BatchedClosestHitCallback::new(|rays, _min_dists, _max_dists| vec![None; rays.len()]);
let batched_any_hit =
BatchedAnyHitCallback::new(|rays, _min_dists, _max_dists| vec![false; rays.len()]);
let callbacks = CustomRayTracingCallbacks::new(
closest_hit,
any_hit,
batched_closest_hit,
batched_any_hit,
);
assert!(Scene::<CustomRayTracer>::try_with_custom(&context, &callbacks).is_ok());
}
#[test]
fn test_scene_clone() {
let context = Context::default();
let scene = Scene::<DefaultRayTracer>::try_new(&context).unwrap();
let clone = scene.clone();
assert_eq!(scene.raw_ptr(), clone.raw_ptr());
drop(scene);
assert!(!clone.raw_ptr().is_null());
}
}