modelio-rs 0.2.1

Safe Rust bindings for Apple's ModelIO framework — assets, meshes, materials, lights, cameras, voxels, textures, and animation on macOS
Documentation
use std::ptr;

use crate::asset::Asset;
use crate::error::Result;
use crate::ffi;
use crate::handle::ObjectHandle;
use crate::light::Light;
use crate::object::Object;
use crate::texture::Texture;
use crate::types::{BoundingBox, ProbePlacement};
use crate::util::required_handle;

type IrradianceCallbackFn = dyn Fn([f32; 3]) -> Vec<f32> + Send + Sync + 'static;

struct IrradianceCallback {
    callback: Box<IrradianceCallbackFn>,
}

#[no_mangle]
pub extern "C" fn mdlx_light_probe_irradiance_data_source_coefficients(
    context: *mut core::ffi::c_void,
    x: f32,
    y: f32,
    z: f32,
    out_values: *mut f32,
    capacity: u64,
) -> u64 {
    let Some(context) = (!context.is_null()).then_some(context.cast::<IrradianceCallback>()) else {
        return 0;
    };
    let values = (unsafe { &*context }.callback)([x, y, z]);
    let total = values.len();
    if out_values.is_null() || capacity == 0 {
        return total as u64;
    }
    let write_count = total.min(capacity as usize);
    unsafe { out_values.copy_from_nonoverlapping(values.as_ptr(), write_count) };
    total as u64
}

#[no_mangle]
pub extern "C" fn mdlx_light_probe_irradiance_data_source_release(context: *mut core::ffi::c_void) {
    if context.is_null() {
        return;
    }
    unsafe { drop(Box::from_raw(context.cast::<IrradianceCallback>())) };
}

fn release_callback_context(context: *mut core::ffi::c_void) {
    mdlx_light_probe_irradiance_data_source_release(context);
}

fn array_objects<T, F>(array_ptr: *mut core::ffi::c_void, context: &'static str, mut map: F) -> Result<Vec<T>>
where
    F: FnMut(ObjectHandle) -> T,
{
    let array = required_handle(array_ptr, context)?;
    let count = unsafe { ffi::mdl_array_count(array.as_ptr()) as usize };
    let mut values = Vec::with_capacity(count);
    for index in 0..count {
        let ptr = unsafe { ffi::mdl_array_object_at(array.as_ptr(), index as u64) };
        if let Some(handle) = unsafe { ObjectHandle::from_retained_ptr(ptr) } {
            values.push(map(handle));
        }
    }
    Ok(values)
}

#[derive(Debug, Clone)]
pub struct LightProbe {
    handle: ObjectHandle,
}

impl LightProbe {
    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
        Self { handle }
    }

    pub fn new(
        reflective_texture: Option<&Texture>,
        irradiance_texture: Option<&Texture>,
    ) -> Result<Self> {
        let mut out_probe = ptr::null_mut();
        let mut out_error = ptr::null_mut();
        let status = unsafe {
            ffi::mdl_light_probe_new(
                reflective_texture.map_or(ptr::null_mut(), Texture::as_ptr),
                irradiance_texture.map_or(ptr::null_mut(), Texture::as_ptr),
                &mut out_probe,
                &mut out_error,
            )
        };
        crate::util::status_result(status, out_error)?;
        Ok(Self::from_handle(required_handle(out_probe, "MDLLightProbe")?))
    }

    pub fn generate_spherical_harmonics_from_irradiance(&self, level: usize) {
        unsafe {
            ffi::mdl_light_probe_generate_spherical_harmonics_from_irradiance(
                self.handle.as_ptr(),
                level as u64,
            );
        }
    }

    #[must_use]
    pub fn reflective_texture(&self) -> Option<Texture> {
        let ptr = unsafe { ffi::mdl_light_probe_reflective_texture(self.handle.as_ptr()) };
        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Texture::from_handle)
    }

    #[must_use]
    pub fn irradiance_texture(&self) -> Option<Texture> {
        let ptr = unsafe { ffi::mdl_light_probe_irradiance_texture(self.handle.as_ptr()) };
        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Texture::from_handle)
    }

    #[must_use]
    pub fn spherical_harmonics_level(&self) -> usize {
        unsafe { ffi::mdl_light_probe_spherical_harmonics_level(self.handle.as_ptr()) as usize }
    }

    #[must_use]
    pub fn spherical_harmonics_coefficients(&self) -> Vec<f32> {
        let count = unsafe {
            ffi::mdl_light_probe_spherical_harmonics_coefficient_count(self.handle.as_ptr()) as usize
        };
        let mut values = vec![0.0_f32; count];
        if values.is_empty() {
            return values;
        }
        let written = unsafe {
            ffi::mdl_light_probe_copy_spherical_harmonics_coefficients(
                self.handle.as_ptr(),
                values.as_mut_ptr(),
                values.len() as u64,
            )
        } as usize;
        values.truncate(written);
        values
    }

    #[must_use]
    pub fn as_light(&self) -> Light {
        Light::from_handle(self.handle.clone())
    }

    #[must_use]
    pub fn as_object(&self) -> Object {
        Object::from_handle(self.handle.clone())
    }
}

#[derive(Debug, Clone)]
pub struct LightProbeIrradianceDataSource {
    handle: ObjectHandle,
}

impl LightProbeIrradianceDataSource {
    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
        Self { handle }
    }

    pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
        self.handle.as_ptr()
    }

    pub fn new<F>(
        bounding_box: BoundingBox,
        spherical_harmonics_level: usize,
        coefficients_at_position: F,
    ) -> Result<Self>
    where
        F: Fn([f32; 3]) -> Vec<f32> + Send + Sync + 'static,
    {
        let callback = Box::new(IrradianceCallback {
            callback: Box::new(coefficients_at_position),
        });
        let callback_ptr = Box::into_raw(callback).cast::<core::ffi::c_void>();
        let mut out_data_source = ptr::null_mut();
        let mut out_error = ptr::null_mut();
        let status = unsafe {
            ffi::mdl_light_probe_irradiance_data_source_new(
                bounding_box.min[0],
                bounding_box.min[1],
                bounding_box.min[2],
                bounding_box.max[0],
                bounding_box.max[1],
                bounding_box.max[2],
                spherical_harmonics_level as u64,
                callback_ptr,
                &mut out_data_source,
                &mut out_error,
            )
        };
        if let Err(error) = crate::util::status_result(status, out_error) {
            release_callback_context(callback_ptr);
            return Err(error);
        }
        match required_handle(out_data_source, "MDLLightProbeIrradianceDataSource") {
            Ok(handle) => Ok(Self::from_handle(handle)),
            Err(error) => {
                release_callback_context(callback_ptr);
                Err(error)
            }
        }
    }

    #[must_use]
    pub fn bounding_box(&self) -> BoundingBox {
        let mut min = [0.0_f32; 3];
        let mut max = [0.0_f32; 3];
        unsafe {
            ffi::mdl_light_probe_irradiance_data_source_bounding_box(
                self.handle.as_ptr(),
                &mut min[0],
                &mut min[1],
                &mut min[2],
                &mut max[0],
                &mut max[1],
                &mut max[2],
            );
        }
        BoundingBox { min, max }
    }

    pub fn set_bounding_box(&self, bounding_box: BoundingBox) {
        unsafe {
            ffi::mdl_light_probe_irradiance_data_source_set_bounding_box(
                self.handle.as_ptr(),
                bounding_box.min[0],
                bounding_box.min[1],
                bounding_box.min[2],
                bounding_box.max[0],
                bounding_box.max[1],
                bounding_box.max[2],
            );
        }
    }

    #[must_use]
    pub fn spherical_harmonics_level(&self) -> usize {
        unsafe {
            ffi::mdl_light_probe_irradiance_data_source_spherical_harmonics_level(
                self.handle.as_ptr(),
            ) as usize
        }
    }

    pub fn set_spherical_harmonics_level(&self, spherical_harmonics_level: usize) {
        unsafe {
            ffi::mdl_light_probe_irradiance_data_source_set_spherical_harmonics_level(
                self.handle.as_ptr(),
                spherical_harmonics_level as u64,
            );
        }
    }
}

impl Asset {
    pub fn place_light_probes(
        density: f32,
        heuristic: ProbePlacement,
        data_source: &LightProbeIrradianceDataSource,
    ) -> Result<Vec<LightProbe>> {
        let ptr = unsafe {
            ffi::mdl_asset_place_light_probes(
                density,
                heuristic.as_raw(),
                data_source.as_ptr(),
            )
        };
        if ptr.is_null() {
            return Ok(Vec::new());
        }
        array_objects(ptr, "MDLAsset light probes", LightProbe::from_handle)
    }
}