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}