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    let values = (unsafe { &*context }.callback)([x, y, z]);
32    let total = values.len();
33    if out_values.is_null() || capacity == 0 {
34        return total as u64;
35    }
36    let write_count = total.min(capacity as usize);
37    unsafe { out_values.copy_from_nonoverlapping(values.as_ptr(), write_count) };
38    total as u64
39}
40
41#[no_mangle]
42pub extern "C" fn mdlx_light_probe_irradiance_data_source_release(context: *mut core::ffi::c_void) {
43    if context.is_null() {
44        return;
45    }
46    unsafe { drop(Box::from_raw(context.cast::<IrradianceCallback>())) };
47}
48
49fn release_callback_context(context: *mut core::ffi::c_void) {
50    mdlx_light_probe_irradiance_data_source_release(context);
51}
52
53fn array_objects<T, F>(array_ptr: *mut core::ffi::c_void, context: &'static str, mut map: F) -> Result<Vec<T>>
54where
55    F: FnMut(ObjectHandle) -> T,
56{
57    let array = required_handle(array_ptr, context)?;
58    let count = unsafe { ffi::mdl_array_count(array.as_ptr()) as usize };
59    let mut values = Vec::with_capacity(count);
60    for index in 0..count {
61        let ptr = unsafe { ffi::mdl_array_object_at(array.as_ptr(), index as u64) };
62        if let Some(handle) = unsafe { ObjectHandle::from_retained_ptr(ptr) } {
63            values.push(map(handle));
64        }
65    }
66    Ok(values)
67}
68
69#[derive(Debug, Clone)]
70pub struct LightProbe {
71    handle: ObjectHandle,
72}
73
74impl LightProbe {
75    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
76        Self { handle }
77    }
78
79    pub fn new(
80        reflective_texture: Option<&Texture>,
81        irradiance_texture: Option<&Texture>,
82    ) -> Result<Self> {
83        let mut out_probe = ptr::null_mut();
84        let mut out_error = ptr::null_mut();
85        let status = unsafe {
86            ffi::mdl_light_probe_new(
87                reflective_texture.map_or(ptr::null_mut(), Texture::as_ptr),
88                irradiance_texture.map_or(ptr::null_mut(), Texture::as_ptr),
89                &mut out_probe,
90                &mut out_error,
91            )
92        };
93        crate::util::status_result(status, out_error)?;
94        Ok(Self::from_handle(required_handle(out_probe, "MDLLightProbe")?))
95    }
96
97    pub fn generate_spherical_harmonics_from_irradiance(&self, level: usize) {
98        unsafe {
99            ffi::mdl_light_probe_generate_spherical_harmonics_from_irradiance(
100                self.handle.as_ptr(),
101                level as u64,
102            );
103        }
104    }
105
106    #[must_use]
107    pub fn reflective_texture(&self) -> Option<Texture> {
108        let ptr = unsafe { ffi::mdl_light_probe_reflective_texture(self.handle.as_ptr()) };
109        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Texture::from_handle)
110    }
111
112    #[must_use]
113    pub fn irradiance_texture(&self) -> Option<Texture> {
114        let ptr = unsafe { ffi::mdl_light_probe_irradiance_texture(self.handle.as_ptr()) };
115        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Texture::from_handle)
116    }
117
118    #[must_use]
119    pub fn spherical_harmonics_level(&self) -> usize {
120        unsafe { ffi::mdl_light_probe_spherical_harmonics_level(self.handle.as_ptr()) as usize }
121    }
122
123    #[must_use]
124    pub fn spherical_harmonics_coefficients(&self) -> Vec<f32> {
125        let count = unsafe {
126            ffi::mdl_light_probe_spherical_harmonics_coefficient_count(self.handle.as_ptr()) as usize
127        };
128        let mut values = vec![0.0_f32; count];
129        if values.is_empty() {
130            return values;
131        }
132        let written = unsafe {
133            ffi::mdl_light_probe_copy_spherical_harmonics_coefficients(
134                self.handle.as_ptr(),
135                values.as_mut_ptr(),
136                values.len() as u64,
137            )
138        } as usize;
139        values.truncate(written);
140        values
141    }
142
143    #[must_use]
144    pub fn as_light(&self) -> Light {
145        Light::from_handle(self.handle.clone())
146    }
147
148    #[must_use]
149    pub fn as_object(&self) -> Object {
150        Object::from_handle(self.handle.clone())
151    }
152}
153
154#[derive(Debug, Clone)]
155pub struct LightProbeIrradianceDataSource {
156    handle: ObjectHandle,
157}
158
159impl LightProbeIrradianceDataSource {
160    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
161        Self { handle }
162    }
163
164    pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
165        self.handle.as_ptr()
166    }
167
168    pub fn new<F>(
169        bounding_box: BoundingBox,
170        spherical_harmonics_level: usize,
171        coefficients_at_position: F,
172    ) -> Result<Self>
173    where
174        F: Fn([f32; 3]) -> Vec<f32> + Send + Sync + 'static,
175    {
176        let callback = Box::new(IrradianceCallback {
177            callback: Box::new(coefficients_at_position),
178        });
179        let callback_ptr = Box::into_raw(callback).cast::<core::ffi::c_void>();
180        let mut out_data_source = ptr::null_mut();
181        let mut out_error = ptr::null_mut();
182        let status = unsafe {
183            ffi::mdl_light_probe_irradiance_data_source_new(
184                bounding_box.min[0],
185                bounding_box.min[1],
186                bounding_box.min[2],
187                bounding_box.max[0],
188                bounding_box.max[1],
189                bounding_box.max[2],
190                spherical_harmonics_level as u64,
191                callback_ptr,
192                &mut out_data_source,
193                &mut out_error,
194            )
195        };
196        if let Err(error) = crate::util::status_result(status, out_error) {
197            release_callback_context(callback_ptr);
198            return Err(error);
199        }
200        match required_handle(out_data_source, "MDLLightProbeIrradianceDataSource") {
201            Ok(handle) => Ok(Self::from_handle(handle)),
202            Err(error) => {
203                release_callback_context(callback_ptr);
204                Err(error)
205            }
206        }
207    }
208
209    #[must_use]
210    pub fn bounding_box(&self) -> BoundingBox {
211        let mut min = [0.0_f32; 3];
212        let mut max = [0.0_f32; 3];
213        unsafe {
214            ffi::mdl_light_probe_irradiance_data_source_bounding_box(
215                self.handle.as_ptr(),
216                &mut min[0],
217                &mut min[1],
218                &mut min[2],
219                &mut max[0],
220                &mut max[1],
221                &mut max[2],
222            );
223        }
224        BoundingBox { min, max }
225    }
226
227    pub fn set_bounding_box(&self, bounding_box: BoundingBox) {
228        unsafe {
229            ffi::mdl_light_probe_irradiance_data_source_set_bounding_box(
230                self.handle.as_ptr(),
231                bounding_box.min[0],
232                bounding_box.min[1],
233                bounding_box.min[2],
234                bounding_box.max[0],
235                bounding_box.max[1],
236                bounding_box.max[2],
237            );
238        }
239    }
240
241    #[must_use]
242    pub fn spherical_harmonics_level(&self) -> usize {
243        unsafe {
244            ffi::mdl_light_probe_irradiance_data_source_spherical_harmonics_level(
245                self.handle.as_ptr(),
246            ) as usize
247        }
248    }
249
250    pub fn set_spherical_harmonics_level(&self, spherical_harmonics_level: usize) {
251        unsafe {
252            ffi::mdl_light_probe_irradiance_data_source_set_spherical_harmonics_level(
253                self.handle.as_ptr(),
254                spherical_harmonics_level as u64,
255            );
256        }
257    }
258}
259
260impl Asset {
261    pub fn place_light_probes(
262        density: f32,
263        heuristic: ProbePlacement,
264        data_source: &LightProbeIrradianceDataSource,
265    ) -> Result<Vec<LightProbe>> {
266        let ptr = unsafe {
267            ffi::mdl_asset_place_light_probes(
268                density,
269                heuristic.as_raw(),
270                data_source.as_ptr(),
271            )
272        };
273        if ptr.is_null() {
274            return Ok(Vec::new());
275        }
276        array_objects(ptr, "MDLAsset light probes", LightProbe::from_handle)
277    }
278}