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]);
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 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 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 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 let ptr = unsafe { ffi::mdl_array_object_at(array.as_ptr(), index as u64) };
71 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)]
80pub struct LightProbe {
82 handle: ObjectHandle,
83}
84
85impl LightProbe {
86 pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
88 Self { handle }
89 }
90
91 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 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 pub fn generate_spherical_harmonics_from_irradiance(&self, level: usize) {
116 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 pub fn reflective_texture(&self) -> Option<Texture> {
128 let ptr = unsafe { ffi::mdl_light_probe_reflective_texture(self.handle.as_ptr()) };
130 unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Texture::from_handle)
132 }
133
134 #[must_use]
135 pub fn irradiance_texture(&self) -> Option<Texture> {
137 let ptr = unsafe { ffi::mdl_light_probe_irradiance_texture(self.handle.as_ptr()) };
139 unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Texture::from_handle)
141 }
142
143 #[must_use]
144 pub fn spherical_harmonics_level(&self) -> usize {
146 unsafe { ffi::mdl_light_probe_spherical_harmonics_level(self.handle.as_ptr()) as usize }
148 }
149
150 #[must_use]
151 pub fn spherical_harmonics_coefficients(&self) -> Vec<f32> {
153 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 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 pub fn as_light(&self) -> Light {
177 Light::from_handle(self.handle.clone())
178 }
179
180 #[must_use]
181 pub fn as_object(&self) -> Object {
183 Object::from_handle(self.handle.clone())
184 }
185}
186
187#[derive(Debug, Clone)]
188pub struct LightProbeIrradianceDataSource {
190 handle: ObjectHandle,
191}
192
193impl LightProbeIrradianceDataSource {
194 pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
196 Self { handle }
197 }
198
199 pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
201 self.handle.as_ptr()
202 }
203
204 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 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 pub fn bounding_box(&self) -> BoundingBox {
250 let mut min = [0.0_f32; 3];
251 let mut max = [0.0_f32; 3];
252 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 pub fn set_bounding_box(&self, bounding_box: BoundingBox) {
269 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 pub fn spherical_harmonics_level(&self) -> usize {
286 unsafe {
288 ffi::mdl_light_probe_irradiance_data_source_spherical_harmonics_level(
289 self.handle.as_ptr(),
290 ) as usize
291 }
292 }
293
294 pub fn set_spherical_harmonics_level(&self, spherical_harmonics_level: usize) {
296 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 pub fn place_light_probes(
309 density: f32,
310 heuristic: ProbePlacement,
311 data_source: &LightProbeIrradianceDataSource,
312 ) -> Result<Vec<LightProbe>> {
313 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}