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