Skip to main content

modelio/
texture.rs

1use std::path::Path;
2use std::ptr;
3
4use crate::error::Result;
5use crate::ffi;
6use crate::handle::ObjectHandle;
7use crate::types::{TextureChannelEncoding, TextureInfo};
8use crate::util::{c_string, parse_json, path_to_c_string, required_handle};
9
10#[derive(Debug, Clone)]
11/// Wraps the corresponding Model I/O texture counterpart.
12pub struct Texture {
13    handle: ObjectHandle,
14}
15
16impl Texture {
17    /// Builds this wrapper from the retained handle of the wrapped Model I/O texture counterpart.
18    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
19        Self { handle }
20    }
21
22    /// Returns the opaque pointer used to call the wrapped Model I/O texture counterpart.
23    pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
24        self.handle.as_ptr()
25    }
26
27    /// Calls the corresponding Model I/O method on the wrapped Model I/O texture counterpart.
28    pub fn from_url(path: impl AsRef<Path>, name: Option<&str>) -> Result<Self> {
29        let path = path_to_c_string(path.as_ref())?;
30        let name = name.map(c_string).transpose()?;
31        let mut out_texture = ptr::null_mut();
32        let mut out_error = ptr::null_mut();
33        // SAFETY: The unsafe operation is valid in this context.
34        let status = unsafe {
35            ffi::mdl_url_texture_new(
36                path.as_ptr(),
37                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
38                &mut out_texture,
39                &mut out_error,
40            )
41        };
42        crate::util::status_result(status, out_error)?;
43        Ok(Self::from_handle(required_handle(
44            out_texture,
45            "MDLURLTexture",
46        )?))
47    }
48
49    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O texture counterpart.
50    pub fn new_checkerboard(
51        divisions: f32,
52        name: Option<&str>,
53        dimensions: [i32; 2],
54        channel_count: usize,
55        channel_encoding: TextureChannelEncoding,
56        color1: [f32; 4],
57        color2: [f32; 4],
58    ) -> Result<Self> {
59        let name = name.map(c_string).transpose()?;
60        let mut out_texture = ptr::null_mut();
61        let mut out_error = ptr::null_mut();
62        // SAFETY: The unsafe operation is valid in this context.
63        let status = unsafe {
64            ffi::mdl_checkerboard_texture_new(
65                divisions,
66                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
67                dimensions[0],
68                dimensions[1],
69                channel_count as u64,
70                channel_encoding as i32,
71                color1[0],
72                color1[1],
73                color1[2],
74                color1[3],
75                color2[0],
76                color2[1],
77                color2[2],
78                color2[3],
79                &mut out_texture,
80                &mut out_error,
81            )
82        };
83        crate::util::status_result(status, out_error)?;
84        Ok(Self::from_handle(required_handle(
85            out_texture,
86            "MDLCheckerboardTexture",
87        )?))
88    }
89
90    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O texture counterpart.
91    pub fn new_color_temperature_gradient(
92        color_temperature1: f32,
93        color_temperature2: f32,
94        name: Option<&str>,
95        dimensions: [i32; 2],
96    ) -> Result<Self> {
97        let name = name.map(c_string).transpose()?;
98        let mut out_texture = ptr::null_mut();
99        let mut out_error = ptr::null_mut();
100        // SAFETY: The unsafe operation is valid in this context.
101        let status = unsafe {
102            ffi::mdl_color_swatch_texture_new_temperature_gradient(
103                color_temperature1,
104                color_temperature2,
105                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
106                dimensions[0],
107                dimensions[1],
108                &mut out_texture,
109                &mut out_error,
110            )
111        };
112        crate::util::status_result(status, out_error)?;
113        Ok(Self::from_handle(required_handle(
114            out_texture,
115            "MDLColorSwatchTexture",
116        )?))
117    }
118
119    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O texture counterpart.
120    pub fn new_color_gradient(
121        color1: [f32; 4],
122        color2: [f32; 4],
123        name: Option<&str>,
124        dimensions: [i32; 2],
125    ) -> Result<Self> {
126        let name = name.map(c_string).transpose()?;
127        let mut out_texture = ptr::null_mut();
128        let mut out_error = ptr::null_mut();
129        // SAFETY: The unsafe operation is valid in this context.
130        let status = unsafe {
131            ffi::mdl_color_swatch_texture_new_color_gradient(
132                color1[0],
133                color1[1],
134                color1[2],
135                color1[3],
136                color2[0],
137                color2[1],
138                color2[2],
139                color2[3],
140                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
141                dimensions[0],
142                dimensions[1],
143                &mut out_texture,
144                &mut out_error,
145            )
146        };
147        crate::util::status_result(status, out_error)?;
148        Ok(Self::from_handle(required_handle(
149            out_texture,
150            "MDLColorSwatchTexture",
151        )?))
152    }
153
154    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O texture counterpart.
155    pub fn new_vector_noise(
156        smoothness: f32,
157        name: Option<&str>,
158        dimensions: [i32; 2],
159        channel_encoding: TextureChannelEncoding,
160    ) -> Result<Self> {
161        let name = name.map(c_string).transpose()?;
162        let mut out_texture = ptr::null_mut();
163        let mut out_error = ptr::null_mut();
164        // SAFETY: The unsafe operation is valid in this context.
165        let status = unsafe {
166            ffi::mdl_noise_texture_new_vector(
167                smoothness,
168                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
169                dimensions[0],
170                dimensions[1],
171                channel_encoding.as_raw(),
172                &mut out_texture,
173                &mut out_error,
174            )
175        };
176        crate::util::status_result(status, out_error)?;
177        Ok(Self::from_handle(required_handle(
178            out_texture,
179            "MDLNoiseTexture",
180        )?))
181    }
182
183    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O texture counterpart.
184    pub fn new_scalar_noise(
185        smoothness: f32,
186        name: Option<&str>,
187        dimensions: [i32; 2],
188        channel_count: usize,
189        channel_encoding: TextureChannelEncoding,
190        grayscale: bool,
191    ) -> Result<Self> {
192        let name = name.map(c_string).transpose()?;
193        let mut out_texture = ptr::null_mut();
194        let mut out_error = ptr::null_mut();
195        // SAFETY: The unsafe operation is valid in this context.
196        let status = unsafe {
197            ffi::mdl_noise_texture_new_scalar(
198                smoothness,
199                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
200                dimensions[0],
201                dimensions[1],
202                channel_count as u64,
203                channel_encoding.as_raw(),
204                i32::from(grayscale),
205                &mut out_texture,
206                &mut out_error,
207            )
208        };
209        crate::util::status_result(status, out_error)?;
210        Ok(Self::from_handle(required_handle(
211            out_texture,
212            "MDLNoiseTexture",
213        )?))
214    }
215
216    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O texture counterpart.
217    pub fn new_cellular_noise(
218        frequency: f32,
219        name: Option<&str>,
220        dimensions: [i32; 2],
221        channel_encoding: TextureChannelEncoding,
222    ) -> Result<Self> {
223        let name = name.map(c_string).transpose()?;
224        let mut out_texture = ptr::null_mut();
225        let mut out_error = ptr::null_mut();
226        // SAFETY: The unsafe operation is valid in this context.
227        let status = unsafe {
228            ffi::mdl_noise_texture_new_cellular(
229                frequency,
230                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
231                dimensions[0],
232                dimensions[1],
233                channel_encoding.as_raw(),
234                &mut out_texture,
235                &mut out_error,
236            )
237        };
238        crate::util::status_result(status, out_error)?;
239        Ok(Self::from_handle(required_handle(
240            out_texture,
241            "MDLNoiseTexture",
242        )?))
243    }
244
245    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O texture counterpart.
246    pub fn new_normal_map(
247        source_texture: &Self,
248        name: Option<&str>,
249        smoothness: f32,
250        contrast: f32,
251    ) -> Result<Self> {
252        let name = name.map(c_string).transpose()?;
253        let mut out_texture = ptr::null_mut();
254        let mut out_error = ptr::null_mut();
255        // SAFETY: The unsafe operation is valid in this context.
256        let status = unsafe {
257            ffi::mdl_normal_map_texture_new(
258                source_texture.as_ptr(),
259                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
260                smoothness,
261                contrast,
262                &mut out_texture,
263                &mut out_error,
264            )
265        };
266        crate::util::status_result(status, out_error)?;
267        Ok(Self::from_handle(required_handle(
268            out_texture,
269            "MDLNormalMapTexture",
270        )?))
271    }
272
273    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O texture counterpart.
274    pub fn new_sky_cube(
275        name: Option<&str>,
276        dimensions: [i32; 2],
277        channel_encoding: TextureChannelEncoding,
278        turbidity: f32,
279        sun_elevation: f32,
280        upper_atmosphere_scattering: f32,
281        ground_albedo: f32,
282    ) -> Result<Self> {
283        let name = name.map(c_string).transpose()?;
284        let mut out_texture = ptr::null_mut();
285        let mut out_error = ptr::null_mut();
286        // SAFETY: The unsafe operation is valid in this context.
287        let status = unsafe {
288            ffi::mdl_sky_cube_texture_new(
289                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
290                channel_encoding.as_raw(),
291                dimensions[0],
292                dimensions[1],
293                turbidity,
294                sun_elevation,
295                upper_atmosphere_scattering,
296                ground_albedo,
297                &mut out_texture,
298                &mut out_error,
299            )
300        };
301        crate::util::status_result(status, out_error)?;
302        Ok(Self::from_handle(required_handle(
303            out_texture,
304            "MDLSkyCubeTexture",
305        )?))
306    }
307
308    #[allow(clippy::too_many_arguments)]
309    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O texture counterpart.
310    pub fn new_sky_cube_with_azimuth(
311        name: Option<&str>,
312        dimensions: [i32; 2],
313        channel_encoding: TextureChannelEncoding,
314        turbidity: f32,
315        sun_elevation: f32,
316        sun_azimuth: f32,
317        upper_atmosphere_scattering: f32,
318        ground_albedo: f32,
319    ) -> Result<Self> {
320        let name = name.map(c_string).transpose()?;
321        let mut out_texture = ptr::null_mut();
322        let mut out_error = ptr::null_mut();
323        // SAFETY: The unsafe operation is valid in this context.
324        let status = unsafe {
325            ffi::mdl_sky_cube_texture_new_with_azimuth(
326                name.as_ref().map_or(ptr::null(), |name| name.as_ptr()),
327                channel_encoding.as_raw(),
328                dimensions[0],
329                dimensions[1],
330                turbidity,
331                sun_elevation,
332                sun_azimuth,
333                upper_atmosphere_scattering,
334                ground_albedo,
335                &mut out_texture,
336                &mut out_error,
337            )
338        };
339        crate::util::status_result(status, out_error)?;
340        Ok(Self::from_handle(required_handle(
341            out_texture,
342            "MDLSkyCubeTexture",
343        )?))
344    }
345
346    /// Calls the corresponding Model I/O method on the wrapped Model I/O texture counterpart.
347    pub fn update_sky_cube(&self) {
348        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
349        unsafe { ffi::mdl_sky_cube_texture_update(self.as_ptr()) };
350    }
351
352    /// Calls the corresponding Model I/O method on the wrapped Model I/O texture counterpart.
353    pub fn info(&self) -> Result<TextureInfo> {
354        parse_json(
355            // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
356            unsafe { ffi::mdl_texture_info_json(self.handle.as_ptr()) },
357            "MDLTexture",
358        )
359    }
360
361    /// Calls the corresponding Model I/O method on the wrapped Model I/O texture counterpart.
362    pub fn write_to_url(&self, path: impl AsRef<Path>) -> Result<()> {
363        let path = path_to_c_string(path.as_ref())?;
364        let mut out_error = ptr::null_mut();
365        // SAFETY: The unsafe operation is valid in this context.
366        let status = unsafe {
367            ffi::mdl_texture_write_to_url(self.handle.as_ptr(), path.as_ptr(), &mut out_error)
368        };
369        crate::util::status_result(status, out_error)
370    }
371
372    fn texel_data(&self, top_left_origin: bool) -> Vec<u8> {
373        // SAFETY: The unsafe operation is valid in this context.
374        let length = unsafe {
375            ffi::mdl_texture_texel_data_length(self.handle.as_ptr(), i32::from(top_left_origin))
376        } as usize;
377        let mut bytes = vec![0_u8; length];
378        if length == 0 {
379            return bytes;
380        }
381        // SAFETY: The unsafe operation is valid in this context.
382        let written = unsafe {
383            ffi::mdl_texture_copy_texel_data(
384                self.handle.as_ptr(),
385                i32::from(top_left_origin),
386                bytes.as_mut_ptr(),
387                bytes.len() as u64,
388            )
389        } as usize;
390        bytes.truncate(written);
391        bytes
392    }
393
394    #[must_use]
395    /// Calls the corresponding Model I/O method on the wrapped Model I/O texture counterpart.
396    pub fn texel_data_top_left(&self) -> Vec<u8> {
397        self.texel_data(true)
398    }
399
400    #[must_use]
401    /// Calls the corresponding Model I/O method on the wrapped Model I/O texture counterpart.
402    pub fn texel_data_bottom_left(&self) -> Vec<u8> {
403        self.texel_data(false)
404    }
405}