Skip to main content

modelio/
light_probe.rs

1use std::ptr;
2
3use crate::asset::Asset;
4use crate::error::Result;
5use crate::ffi;
6use crate::handle::ObjectHandle;
7use crate::light::Light;
8use crate::object::Object;
9use crate::texture::Texture;
10use crate::types::{BoundingBox, ProbePlacement};
11use crate::util::required_handle;
12
13type IrradianceCallbackFn = dyn Fn([f32; 3]) -> Vec<f32> + Send + Sync + 'static;
14
15struct IrradianceCallback {
16    callback: Box<IrradianceCallbackFn>,
17}
18
19#[no_mangle]
20pub extern "C" fn mdlx_light_probe_irradiance_data_source_coefficients(
21    context: *mut core::ffi::c_void,
22    x: f32,
23    y: f32,
24    z: f32,
25    out_values: *mut f32,
26    capacity: u64,
27) -> u64 {
28    let Some(context) = (!context.is_null()).then_some(context.cast::<IrradianceCallback>()) else {
29        return 0;
30    };
31    // SAFETY: The unsafe operation is valid in this context.
32    let values = (unsafe { &*context }.callback)([x, y, z]);
33    let total = values.len();
34    if out_values.is_null() || capacity == 0 {
35        return total as u64;
36    }
37    let write_count = total.min(capacity as usize);
38    // SAFETY: The unsafe operation is valid in this context.
39    unsafe { out_values.copy_from_nonoverlapping(values.as_ptr(), write_count) };
40    total as u64
41}
42
43#[no_mangle]
44pub extern "C" fn mdlx_light_probe_irradiance_data_source_release(context: *mut core::ffi::c_void) {
45    if context.is_null() {
46        return;
47    }
48    // SAFETY: The unsafe operation is valid in this context.
49    unsafe { drop(Box::from_raw(context.cast::<IrradianceCallback>())) };
50}
51
52fn release_callback_context(context: *mut core::ffi::c_void) {
53    mdlx_light_probe_irradiance_data_source_release(context);
54}
55
56fn array_objects<T, F>(
57    array_ptr: *mut core::ffi::c_void,
58    context: &'static str,
59    mut map: F,
60) -> Result<Vec<T>>
61where
62    F: FnMut(ObjectHandle) -> T,
63{
64    let array = required_handle(array_ptr, context)?;
65    // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
66    let count = unsafe { ffi::mdl_array_count(array.as_ptr()) as usize };
67    let mut values = Vec::with_capacity(count);
68    for index in 0..count {
69        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
70        let ptr = unsafe { ffi::mdl_array_object_at(array.as_ptr(), index as u64) };
71        // SAFETY: The unsafe operation is valid in this context.
72        if let Some(handle) = unsafe { ObjectHandle::from_retained_ptr(ptr) } {
73            values.push(map(handle));
74        }
75    }
76    Ok(values)
77}
78
79#[derive(Debug, Clone)]
80/// Wraps the corresponding Model I/O light probe counterpart.
81pub struct LightProbe {
82    handle: ObjectHandle,
83}
84
85impl LightProbe {
86    /// Builds this wrapper from the retained handle of the wrapped Model I/O light probe counterpart.
87    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
88        Self { handle }
89    }
90
91    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O light probe counterpart.
92    pub fn new(
93        reflective_texture: Option<&Texture>,
94        irradiance_texture: Option<&Texture>,
95    ) -> Result<Self> {
96        let mut out_probe = ptr::null_mut();
97        let mut out_error = ptr::null_mut();
98        // SAFETY: The unsafe operation is valid in this context.
99        let status = unsafe {
100            ffi::mdl_light_probe_new(
101                reflective_texture.map_or(ptr::null_mut(), Texture::as_ptr),
102                irradiance_texture.map_or(ptr::null_mut(), Texture::as_ptr),
103                &mut out_probe,
104                &mut out_error,
105            )
106        };
107        crate::util::status_result(status, out_error)?;
108        Ok(Self::from_handle(required_handle(
109            out_probe,
110            "MDLLightProbe",
111        )?))
112    }
113
114    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe counterpart.
115    pub fn generate_spherical_harmonics_from_irradiance(&self, level: usize) {
116        // SAFETY: The unsafe operation is valid in this context.
117        unsafe {
118            ffi::mdl_light_probe_generate_spherical_harmonics_from_irradiance(
119                self.handle.as_ptr(),
120                level as u64,
121            );
122        }
123    }
124
125    #[must_use]
126    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe counterpart.
127    pub fn reflective_texture(&self) -> Option<Texture> {
128        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
129        let ptr = unsafe { ffi::mdl_light_probe_reflective_texture(self.handle.as_ptr()) };
130        // SAFETY: The unsafe operation is valid in this context.
131        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Texture::from_handle)
132    }
133
134    #[must_use]
135    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe counterpart.
136    pub fn irradiance_texture(&self) -> Option<Texture> {
137        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
138        let ptr = unsafe { ffi::mdl_light_probe_irradiance_texture(self.handle.as_ptr()) };
139        // SAFETY: The unsafe operation is valid in this context.
140        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Texture::from_handle)
141    }
142
143    #[must_use]
144    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe counterpart.
145    pub fn spherical_harmonics_level(&self) -> usize {
146        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
147        unsafe { ffi::mdl_light_probe_spherical_harmonics_level(self.handle.as_ptr()) as usize }
148    }
149
150    #[must_use]
151    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe counterpart.
152    pub fn spherical_harmonics_coefficients(&self) -> Vec<f32> {
153        // SAFETY: The unsafe operation is valid in this context.
154        let count = unsafe {
155            ffi::mdl_light_probe_spherical_harmonics_coefficient_count(self.handle.as_ptr())
156                as usize
157        };
158        let mut values = vec![0.0_f32; count];
159        if values.is_empty() {
160            return values;
161        }
162        // SAFETY: The unsafe operation is valid in this context.
163        let written = unsafe {
164            ffi::mdl_light_probe_copy_spherical_harmonics_coefficients(
165                self.handle.as_ptr(),
166                values.as_mut_ptr(),
167                values.len() as u64,
168            )
169        } as usize;
170        values.truncate(written);
171        values
172    }
173
174    #[must_use]
175    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe counterpart.
176    pub fn as_light(&self) -> Light {
177        Light::from_handle(self.handle.clone())
178    }
179
180    #[must_use]
181    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe counterpart.
182    pub fn as_object(&self) -> Object {
183        Object::from_handle(self.handle.clone())
184    }
185}
186
187#[derive(Debug, Clone)]
188/// Wraps the corresponding Model I/O light probe irradiance data source counterpart.
189pub struct LightProbeIrradianceDataSource {
190    handle: ObjectHandle,
191}
192
193impl LightProbeIrradianceDataSource {
194    /// Builds this wrapper from the retained handle of the wrapped Model I/O light probe irradiance data source counterpart.
195    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
196        Self { handle }
197    }
198
199    /// Returns the opaque pointer used to call the wrapped Model I/O light probe irradiance data source counterpart.
200    pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
201        self.handle.as_ptr()
202    }
203
204    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe irradiance data source counterpart.
205    pub fn new<F>(
206        bounding_box: BoundingBox,
207        spherical_harmonics_level: usize,
208        coefficients_at_position: F,
209    ) -> Result<Self>
210    where
211        F: Fn([f32; 3]) -> Vec<f32> + Send + Sync + 'static,
212    {
213        let callback = Box::new(IrradianceCallback {
214            callback: Box::new(coefficients_at_position),
215        });
216        let callback_ptr = Box::into_raw(callback).cast::<core::ffi::c_void>();
217        let mut out_data_source = ptr::null_mut();
218        let mut out_error = ptr::null_mut();
219        // SAFETY: The unsafe operation is valid in this context.
220        let status = unsafe {
221            ffi::mdl_light_probe_irradiance_data_source_new(
222                bounding_box.min[0],
223                bounding_box.min[1],
224                bounding_box.min[2],
225                bounding_box.max[0],
226                bounding_box.max[1],
227                bounding_box.max[2],
228                spherical_harmonics_level as u64,
229                callback_ptr,
230                &mut out_data_source,
231                &mut out_error,
232            )
233        };
234        if let Err(error) = crate::util::status_result(status, out_error) {
235            release_callback_context(callback_ptr);
236            return Err(error);
237        }
238        match required_handle(out_data_source, "MDLLightProbeIrradianceDataSource") {
239            Ok(handle) => Ok(Self::from_handle(handle)),
240            Err(error) => {
241                release_callback_context(callback_ptr);
242                Err(error)
243            }
244        }
245    }
246
247    #[must_use]
248    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe irradiance data source counterpart.
249    pub fn bounding_box(&self) -> BoundingBox {
250        let mut min = [0.0_f32; 3];
251        let mut max = [0.0_f32; 3];
252        // SAFETY: The unsafe operation is valid in this context.
253        unsafe {
254            ffi::mdl_light_probe_irradiance_data_source_bounding_box(
255                self.handle.as_ptr(),
256                &mut min[0],
257                &mut min[1],
258                &mut min[2],
259                &mut max[0],
260                &mut max[1],
261                &mut max[2],
262            );
263        }
264        BoundingBox { min, max }
265    }
266
267    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe irradiance data source counterpart.
268    pub fn set_bounding_box(&self, bounding_box: BoundingBox) {
269        // SAFETY: The unsafe operation is valid in this context.
270        unsafe {
271            ffi::mdl_light_probe_irradiance_data_source_set_bounding_box(
272                self.handle.as_ptr(),
273                bounding_box.min[0],
274                bounding_box.min[1],
275                bounding_box.min[2],
276                bounding_box.max[0],
277                bounding_box.max[1],
278                bounding_box.max[2],
279            );
280        }
281    }
282
283    #[must_use]
284    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe irradiance data source counterpart.
285    pub fn spherical_harmonics_level(&self) -> usize {
286        // SAFETY: The unsafe operation is valid in this context.
287        unsafe {
288            ffi::mdl_light_probe_irradiance_data_source_spherical_harmonics_level(
289                self.handle.as_ptr(),
290            ) as usize
291        }
292    }
293
294    /// Calls the corresponding Model I/O method on the wrapped Model I/O light probe irradiance data source counterpart.
295    pub fn set_spherical_harmonics_level(&self, spherical_harmonics_level: usize) {
296        // SAFETY: The unsafe operation is valid in this context.
297        unsafe {
298            ffi::mdl_light_probe_irradiance_data_source_set_spherical_harmonics_level(
299                self.handle.as_ptr(),
300                spherical_harmonics_level as u64,
301            );
302        }
303    }
304}
305
306impl Asset {
307    /// Calls the corresponding Model I/O method on the wrapped Model I/O asset counterpart.
308    pub fn place_light_probes(
309        density: f32,
310        heuristic: ProbePlacement,
311        data_source: &LightProbeIrradianceDataSource,
312    ) -> Result<Vec<LightProbe>> {
313        // SAFETY: The unsafe operation is valid in this context.
314        let ptr = unsafe {
315            ffi::mdl_asset_place_light_probes(density, heuristic.as_raw(), data_source.as_ptr())
316        };
317        if ptr.is_null() {
318            return Ok(Vec::new());
319        }
320        array_objects(ptr, "MDLAsset light probes", LightProbe::from_handle)
321    }
322}