use crate::geometry::{Hit, Ray, Vector3};
use std::cell::Cell;
use std::ffi::c_void;
#[cfg(doc)]
use crate::simulation::Simulator;
macro_rules! callback {
(
$(#[$meta:meta])*
$vis:vis $name:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret:ty)?
) => {
$(#[$meta])*
$vis struct $name {
callback: Box<dyn FnMut($($arg_ty),*) $(-> $ret)? + Send>,
}
impl $name {
pub fn new<F>(f: F) -> Self
where
F: FnMut($($arg_ty),*) $(-> $ret)? + Send + 'static,
{
Self {
callback: Box::new(f),
}
}
unsafe extern "C" fn trampoline(
$($arg: <$arg_ty as $crate::callback::FfiConvert>::FfiType,)*
user_data: *mut c_void,
) $(-> <$ret as $crate::callback::FfiConvert>::FfiType)? {
let callback = &mut *(user_data as *mut Box<dyn FnMut($($arg_ty),*) $(-> $ret)? + Send>);
$(let $arg = <$arg_ty as $crate::callback::FfiConvert>::from_ffi($arg);)*
#[allow(unused_variables)]
let result = callback($($arg),*);
$(return <$ret as $crate::callback::FfiConvert>::to_ffi(result);)?
}
#[allow(dead_code)]
pub(crate) fn as_raw_parts(&self) -> (
unsafe extern "C" fn($(<$arg_ty as $crate::callback::FfiConvert>::FfiType,)* *mut c_void) $(-> <$ret as $crate::callback::FfiConvert>::FfiType)?,
*mut c_void,
) {
(
Self::trampoline,
&self.callback as *const _ as *mut c_void,
)
}
}
impl std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($name))
.field("callback", &"<closure>")
.finish()
}
}
};
}
pub(crate) use callback;
pub(crate) trait FfiConvert {
type FfiType;
#[allow(dead_code)]
fn to_ffi(self) -> Self::FfiType;
#[allow(dead_code)]
fn from_ffi(ffi: Self::FfiType) -> Self;
}
pub(crate) trait FfiPassthrough: Copy + 'static {}
impl<T: FfiPassthrough> FfiConvert for T {
type FfiType = T;
fn to_ffi(self) -> Self::FfiType {
self
}
fn from_ffi(ffi: Self::FfiType) -> Self {
ffi
}
}
impl FfiPassthrough for f32 {}
impl FfiPassthrough for usize {}
impl FfiPassthrough for std::ffi::c_int {}
impl FfiConvert for Vector3 {
type FfiType = audionimbus_sys::IPLVector3;
fn to_ffi(self) -> Self::FfiType {
audionimbus_sys::IPLVector3 {
x: self.x,
y: self.y,
z: self.z,
}
}
fn from_ffi(ffi: Self::FfiType) -> Self {
Vector3 {
x: ffi.x,
y: ffi.y,
z: ffi.z,
}
}
}
impl FfiConvert for bool {
type FfiType = audionimbus_sys::IPLbool;
fn to_ffi(self) -> Self::FfiType {
if self {
audionimbus_sys::IPLbool::IPL_TRUE
} else {
audionimbus_sys::IPLbool::IPL_FALSE
}
}
fn from_ffi(ffi: Self::FfiType) -> Self {
match ffi {
audionimbus_sys::IPLbool::IPL_TRUE => true,
audionimbus_sys::IPLbool::IPL_FALSE => false,
}
}
}
impl FfiConvert for Ray {
type FfiType = audionimbus_sys::IPLRay;
fn to_ffi(self) -> Self::FfiType {
self.into()
}
fn from_ffi(ffi: Self::FfiType) -> Self {
ffi.into()
}
}
callback! {
pub ProgressCallback(progress: f32)
}
callback! {
pub PathingVisualizationCallback(
from: Vector3,
to: Vector3,
occluded: bool,
)
}
callback! {
pub DeviationCallback(
angle: f32,
band: std::ffi::c_int,
) -> f32
}
callback! {
pub DistanceAttenuationCallback(distance: f32) -> f32
}
thread_local! {
static DIRECTIVITY_CALLBACK_PTR: Cell<*mut c_void> = const { Cell::new(std::ptr::null_mut()) };
}
pub struct DirectivityCallback {
callback: Box<dyn FnMut(Vector3) -> f32 + Send>,
}
impl DirectivityCallback {
pub fn new<F>(f: F) -> Self
where
F: FnMut(Vector3) -> f32 + Send + 'static,
{
Self {
callback: Box::new(f),
}
}
unsafe extern "C" fn trampoline(
direction: audionimbus_sys::IPLVector3,
_user_data: *mut c_void,
) -> f32 {
let callback_ptr = DIRECTIVITY_CALLBACK_PTR.get();
let callback = &mut *(callback_ptr as *mut Box<dyn FnMut(Vector3) -> f32 + Send>);
let direction = Vector3::from_ffi(direction);
callback(direction)
}
pub(crate) fn as_raw_parts(
&self,
) -> (
unsafe extern "C" fn(audionimbus_sys::IPLVector3, *mut c_void) -> f32,
*mut c_void,
) {
let callback_ptr = &self.callback as *const _ as *mut c_void;
DIRECTIVITY_CALLBACK_PTR.set(callback_ptr);
(Self::trampoline, callback_ptr)
}
}
impl std::fmt::Debug for DirectivityCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DirectivityCallback")
.field("callback", &"<closure>")
.finish()
}
}
callback! {
pub AirAbsorptionCallback(distance: f32, band: i32) -> f32
}
pub struct CustomRayTracingCallbacks {
closest_hit_callback: ClosestHitCallback,
any_hit_callback: AnyHitCallback,
batched_closest_hit_callback: BatchedClosestHitCallback,
batched_any_hit_callback: BatchedAnyHitCallback,
}
impl CustomRayTracingCallbacks {
pub fn new(
closest_hit: ClosestHitCallback,
any_hit: AnyHitCallback,
batched_closest_hit: BatchedClosestHitCallback,
batched_any_hit: BatchedAnyHitCallback,
) -> Self {
Self {
closest_hit_callback: closest_hit,
any_hit_callback: any_hit,
batched_closest_hit_callback: batched_closest_hit,
batched_any_hit_callback: batched_any_hit,
}
}
pub(crate) fn as_ffi_settings(
&self,
) -> (
audionimbus_sys::IPLSceneSettings,
Box<CustomRayTracingUserData>,
) {
let user_data = Box::new(CustomRayTracingUserData {
closest_hit: &self.closest_hit_callback.callback as *const _ as *mut _,
any_hit: &self.any_hit_callback.callback as *const _ as *mut _,
batched_closest_hit: &self.batched_closest_hit_callback.callback as *const _ as *mut _,
batched_any_hit: &self.batched_any_hit_callback.callback as *const _ as *mut _,
});
let user_data_ptr = &*user_data as *const _ as *mut c_void;
let settings = audionimbus_sys::IPLSceneSettings {
type_: audionimbus_sys::IPLSceneType::IPL_SCENETYPE_CUSTOM,
closestHitCallback: Some(
ClosestHitCallback::trampoline as unsafe extern "C" fn(_, _, _, _, _),
),
anyHitCallback: Some(AnyHitCallback::trampoline as unsafe extern "C" fn(_, _, _, _, _)),
batchedClosestHitCallback: Some(
BatchedClosestHitCallback::trampoline as unsafe extern "C" fn(_, _, _, _, _, _),
),
batchedAnyHitCallback: Some(
BatchedAnyHitCallback::trampoline as unsafe extern "C" fn(_, _, _, _, _, _),
),
userData: user_data_ptr,
embreeDevice: std::ptr::null_mut(),
radeonRaysDevice: std::ptr::null_mut(),
};
(settings, user_data)
}
}
type ClosestHitFn = dyn FnMut(Ray, f32, f32) -> Option<Hit> + Send;
type AnyHitFn = dyn FnMut(Ray, f32, f32) -> bool + Send;
type BatchedClosestHitFn = dyn FnMut(&[Ray], &[f32], &[f32]) -> Vec<Option<Hit>> + Send;
type BatchedAnyHitFn = dyn FnMut(&[Ray], &[f32], &[f32]) -> Vec<bool> + Send;
#[derive(Debug)]
pub(crate) struct CustomRayTracingUserData {
closest_hit: *mut Box<ClosestHitFn>,
any_hit: *mut Box<AnyHitFn>,
batched_closest_hit: *mut Box<BatchedClosestHitFn>,
batched_any_hit: *mut Box<BatchedAnyHitFn>,
}
pub struct ClosestHitCallback {
callback: Box<ClosestHitFn>,
}
impl ClosestHitCallback {
pub fn new<F>(f: F) -> Self
where
F: FnMut(Ray, f32, f32) -> Option<Hit> + Send + 'static,
{
Self {
callback: Box::new(f),
}
}
unsafe extern "C" fn trampoline(
ray: *const audionimbus_sys::IPLRay,
min_distance: f32,
max_distance: f32,
hit: *mut audionimbus_sys::IPLHit,
user_data: *mut c_void,
) {
let all_callbacks = &*(user_data as *const CustomRayTracingUserData);
let callback = &mut *all_callbacks.closest_hit;
let ray = if ray.is_null() {
Ray::default()
} else {
Ray::from(*ray)
};
let result = callback(ray, min_distance, max_distance);
if let Some(hit_result) = result {
if !hit.is_null() {
*hit = audionimbus_sys::IPLHit {
distance: hit_result.distance,
triangleIndex: hit_result.triangle_index.map(|i| i as i32).unwrap_or(-1),
objectIndex: hit_result.object_index.map(|i| i as i32).unwrap_or(-1),
materialIndex: hit_result.material_index.map(|i| i as i32).unwrap_or(-1),
normal: hit_result.normal.into(),
material: std::ptr::null_mut(),
};
}
}
}
}
impl std::fmt::Debug for ClosestHitCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClosestHitCallback")
.field("callback", &"<closure>")
.finish()
}
}
pub struct AnyHitCallback {
callback: Box<AnyHitFn>,
}
impl AnyHitCallback {
pub fn new<F>(f: F) -> Self
where
F: FnMut(Ray, f32, f32) -> bool + Send + 'static,
{
Self {
callback: Box::new(f),
}
}
unsafe extern "C" fn trampoline(
ray: *const audionimbus_sys::IPLRay,
min_distance: f32,
max_distance: f32,
occluded: *mut u8,
user_data: *mut c_void,
) {
let all_callbacks = &*(user_data as *const CustomRayTracingUserData);
let callback = &mut *all_callbacks.any_hit;
let ray = if ray.is_null() {
Ray::default()
} else {
Ray::from(*ray)
};
let result = callback(ray, min_distance, max_distance);
if !occluded.is_null() {
*occluded = if result { 1 } else { 0 };
}
}
}
impl std::fmt::Debug for AnyHitCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AnyHitCallback")
.field("callback", &"<closure>")
.finish()
}
}
pub struct BatchedClosestHitCallback {
callback: Box<BatchedClosestHitFn>,
}
impl BatchedClosestHitCallback {
pub fn new<F>(f: F) -> Self
where
F: FnMut(&[Ray], &[f32], &[f32]) -> Vec<Option<Hit>> + Send + 'static,
{
Self {
callback: Box::new(f),
}
}
unsafe extern "C" fn trampoline(
num_rays: i32,
rays: *const audionimbus_sys::IPLRay,
min_distances: *const f32,
max_distances: *const f32,
hits: *mut audionimbus_sys::IPLHit,
user_data: *mut c_void,
) {
let all_callbacks = &*(user_data as *const CustomRayTracingUserData);
let callback = &mut *all_callbacks.batched_closest_hit;
if num_rays <= 0
|| rays.is_null()
|| min_distances.is_null()
|| max_distances.is_null()
|| hits.is_null()
{
return;
}
let num_rays = num_rays as usize;
let rays_slice = std::slice::from_raw_parts(rays, num_rays)
.iter()
.map(|&r| Ray::from(r))
.collect::<Vec<_>>();
let min_distances_slice = std::slice::from_raw_parts(min_distances, num_rays);
let max_distances_slice = std::slice::from_raw_parts(max_distances, num_rays);
let results = callback(&rays_slice, min_distances_slice, max_distances_slice);
for (i, result) in results.iter().enumerate().take(num_rays) {
if let Some(hit_result) = result {
*hits.add(i) = audionimbus_sys::IPLHit {
distance: hit_result.distance,
triangleIndex: hit_result
.triangle_index
.map(|idx| idx as i32)
.unwrap_or(-1),
objectIndex: hit_result.object_index.map(|idx| idx as i32).unwrap_or(-1),
materialIndex: hit_result
.material_index
.map(|idx| idx as i32)
.unwrap_or(-1),
normal: hit_result.normal.into(),
material: std::ptr::null_mut(),
};
}
}
}
}
impl std::fmt::Debug for BatchedClosestHitCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BatchedClosestHitCallback")
.field("callback", &"<closure>")
.finish()
}
}
pub struct BatchedAnyHitCallback {
callback: Box<BatchedAnyHitFn>,
}
impl BatchedAnyHitCallback {
pub fn new<F>(f: F) -> Self
where
F: FnMut(&[Ray], &[f32], &[f32]) -> Vec<bool> + Send + 'static,
{
Self {
callback: Box::new(f),
}
}
unsafe extern "C" fn trampoline(
num_rays: i32,
rays: *const audionimbus_sys::IPLRay,
min_distances: *const f32,
max_distances: *const f32,
occluded: *mut u8,
user_data: *mut c_void,
) {
let all_callbacks = &*(user_data as *const CustomRayTracingUserData);
let callback = &mut *all_callbacks.batched_any_hit;
if num_rays <= 0
|| rays.is_null()
|| min_distances.is_null()
|| max_distances.is_null()
|| occluded.is_null()
{
return;
}
let num_rays = num_rays as usize;
let rays_slice = std::slice::from_raw_parts(rays, num_rays)
.iter()
.map(|&r| Ray::from(r))
.collect::<Vec<_>>();
let min_distances_slice = std::slice::from_raw_parts(min_distances, num_rays);
let max_distances_slice = std::slice::from_raw_parts(max_distances, num_rays);
let results = callback(&rays_slice, min_distances_slice, max_distances_slice);
for (i, &result) in results.iter().enumerate().take(num_rays) {
*occluded.add(i) = if result { 1 } else { 0 };
}
}
}
impl std::fmt::Debug for BatchedAnyHitCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BatchedAnyHitCallback")
.field("callback", &"<closure>")
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum LogLevel {
Info,
Warning,
Error,
Debug,
}
impl From<audionimbus_sys::IPLLogLevel> for LogLevel {
fn from(level: audionimbus_sys::IPLLogLevel) -> Self {
match level {
audionimbus_sys::IPLLogLevel::IPL_LOGLEVEL_INFO => LogLevel::Info,
audionimbus_sys::IPLLogLevel::IPL_LOGLEVEL_WARNING => LogLevel::Warning,
audionimbus_sys::IPLLogLevel::IPL_LOGLEVEL_ERROR => LogLevel::Error,
audionimbus_sys::IPLLogLevel::IPL_LOGLEVEL_DEBUG => LogLevel::Debug,
}
}
}
#[macro_export]
macro_rules! log_callback {
($name:ident, $closure:expr) => {
$crate::log_callback!(@impl $name, $closure);
};
($closure:expr) => {{
$crate::log_callback!(@impl __log_callback_impl, $closure);
__log_callback_impl
}};
(@impl $name:ident, $closure:expr) => {
unsafe extern "C" fn $name(
level: audionimbus_sys::IPLLogLevel,
message: *const ::std::os::raw::c_char,
) {
let rust_level = $crate::callback::LogLevel::from(level);
let rust_message = if !message.is_null() {
::std::ffi::CStr::from_ptr(message)
.to_str()
.unwrap_or("<invalid UTF-8>")
} else {
"<null>"
};
let closure: fn($crate::callback::LogLevel, &str) = $closure;
closure(rust_level, rust_message);
}
};
}
#[macro_export]
macro_rules! allocate_callback {
($name:ident, $closure:expr) => {
$crate::allocate_callback!(@impl $name, $closure);
};
($closure:expr) => {{
$crate::allocate_callback!(@impl __allocate_callback_impl, $closure);
__allocate_callback_impl
}};
(@impl $name:ident, $closure:expr) => {
unsafe extern "C" fn $name(
size: usize,
alignment: usize,
) -> *mut ::std::ffi::c_void {
let closure: fn(usize, usize) -> *mut ::std::ffi::c_void = $closure;
closure(size, alignment)
}
};
}
#[macro_export]
macro_rules! free_callback {
($name:ident, $closure:expr) => {
$crate::free_callback!(@impl $name, $closure);
};
($closure:expr) => {{
$crate::free_callback!(@impl __free_callback_impl, $closure);
__free_callback_impl
}};
(@impl $name:ident, $closure:expr) => {
unsafe extern "C" fn $name(ptr: *mut ::std::ffi::c_void) {
let closure: fn(*mut ::std::ffi::c_void) = $closure;
closure(ptr);
}
};
}