Skip to main content

three_d_asset/
io.rs

1//!
2//! Contains functionality to load any type of asset runtime as well as parsers for common 3D assets.
3//! Also includes functionality to save data which is limited to native.
4//!
5//!
6//! A typical use-case is to load and deserialize assets:
7//! ```
8//! use three_d_asset::io::*;
9//! use three_d_asset::{Texture2D, Model};
10//!
11//! let mut assets = load(&["test_data/test.png", "test_data/cube.obj"]).unwrap();
12//! let texture: Texture2D = assets.deserialize("test.png").unwrap();
13//! let model: Model = assets.deserialize("cube.obj").unwrap();
14//! ```
15//!
16//! Or serialize and save assets:
17//! ```
18//! use three_d_asset::io::*;
19//! use three_d_asset::{Texture2D, TextureData};
20//!
21//! let texture = Texture2D {
22//!     data: TextureData::RgbaU8(vec![
23//!         [0, 0, 0, 255],
24//!         [255, 0, 0, 255],
25//!         [0, 255, 0, 255],
26//!         [0, 0, 255, 255],
27//!     ]),
28//!     width: 2,
29//!     height: 2,
30//!     ..Default::default()
31//! };
32//! let assets = texture.serialize("test_data/test.png").unwrap();
33//! save(&assets).unwrap();
34//! ```
35//!
36
37mod loader;
38pub use loader::*;
39
40mod raw_assets;
41pub use raw_assets::*;
42
43#[cfg(not(target_arch = "wasm32"))]
44mod saver;
45#[cfg(not(target_arch = "wasm32"))]
46pub use saver::*;
47
48#[cfg(feature = "obj")]
49mod obj;
50
51#[cfg(feature = "stl")]
52mod stl;
53
54#[cfg(feature = "gltf")]
55mod gltf;
56
57#[cfg(feature = "image")]
58mod img;
59
60#[cfg(feature = "vol")]
61mod vol;
62
63#[cfg(feature = "pcd")]
64mod pcd;
65
66#[cfg(feature = "3mf")]
67#[path = "io/3mf.rs"]
68mod three_mf;
69
70///
71/// Deserialize a single file from raw bytes. The key is used to determine the type of file.
72///
73/// If the file depends on other files, use [RawAssets::insert] to insert the bytes for each of them in [RawAssets] before deserializing.
74///
75pub fn deserialize<T: Deserialize>(key: &str, bytes: Vec<u8>) -> crate::Result<T> {
76    let mut assets = RawAssets::new();
77    assets.insert(key, bytes);
78    assets.deserialize(key)
79}
80
81///
82/// Loads and deserialize a single file. If the file depends on other files, those files are also loaded.
83///
84#[cfg(not(target_arch = "wasm32"))]
85pub fn load_and_deserialize<T: Deserialize>(path: impl AsRef<std::path::Path>) -> crate::Result<T> {
86    load(&[&path])?.deserialize(path)
87}
88
89///
90/// Async loads and deserialize a single file. If the file depends on other files, those files are also loaded.
91///
92pub async fn load_and_deserialize_async<T: Deserialize>(
93    path: impl AsRef<std::path::Path>,
94) -> crate::Result<T> {
95    load_async(&[&path]).await?.deserialize(path)
96}
97
98///
99/// Save and serialize a single file.
100///
101#[cfg(not(target_arch = "wasm32"))]
102pub fn serialize_and_save<T: Serialize>(
103    path: impl AsRef<std::path::Path>,
104    data: T,
105) -> crate::Result<()> {
106    save(&data.serialize(path)?)
107}
108
109///
110/// Implemented for assets that can be deserialized after being loaded (see also [load] and [RawAssets::deserialize]).
111///
112pub trait Deserialize: Sized {
113    ///
114    /// See [RawAssets::deserialize].
115    ///
116    fn deserialize(
117        path: impl AsRef<std::path::Path>,
118        raw_assets: &mut RawAssets,
119    ) -> crate::Result<Self>;
120}
121
122///
123/// Implemented for assets that can be serialized before being saved (see also [save]).
124///
125pub trait Serialize: Sized {
126    ///
127    /// Serialize the asset into a list of raw assets which consist of byte arrays and related path to where they should be saved (see also [save]).
128    /// The path given as input is the path to the main raw asset.
129    ///
130    fn serialize(&self, path: impl AsRef<std::path::Path>) -> crate::Result<RawAssets>;
131}
132
133use crate::{Error, Geometry, Result};
134use std::collections::HashSet;
135use std::path::{Path, PathBuf};
136
137impl Deserialize for crate::Texture2D {
138    fn deserialize(path: impl AsRef<std::path::Path>, raw_assets: &mut RawAssets) -> Result<Self> {
139        let path = raw_assets.match_path(path.as_ref())?;
140        let extension = path
141            .extension()
142            .map(|e| e.to_str().unwrap())
143            .unwrap_or("image")
144            .to_string();
145        let data_url_bytes = if is_data_url(&path) {
146            Some(parse_data_url(path.to_str().unwrap())?)
147        } else {
148            None
149        };
150
151        #[allow(unused_variables)]
152        let bytes = if let Some(bytes) = data_url_bytes.as_ref() {
153            bytes
154        } else {
155            raw_assets.get(&path)?
156        };
157
158        if "svg" == extension {
159            // to satisfy the compiler during wasm compile
160            #[cfg(not(feature = "svg"))]
161            return Err(Error::FeatureMissing("svg".to_string()));
162
163            #[cfg(feature = "svg")]
164            img::deserialize_svg(path, bytes)
165        } else {
166            #[cfg(not(feature = "image"))]
167            return Err(Error::FeatureMissing(extension));
168
169            #[cfg(feature = "image")]
170            img::deserialize_img(path, bytes)
171        }
172    }
173}
174
175impl Serialize for crate::Texture2D {
176    fn serialize(&self, path: impl AsRef<Path>) -> Result<RawAssets> {
177        let path = path.as_ref();
178
179        #[cfg(not(feature = "image"))]
180        return Err(Error::FeatureMissing(
181            path.extension()
182                .map(|e| e.to_str().unwrap())
183                .unwrap_or("image")
184                .to_string(),
185        ));
186
187        #[cfg(feature = "image")]
188        img::serialize_img(self, path)
189    }
190}
191
192impl Deserialize for crate::Scene {
193    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
194        let path = raw_assets.match_path(path.as_ref())?;
195        match path.extension().map(|e| e.to_str().unwrap()).unwrap_or("") {
196            "gltf" | "glb" => {
197                #[cfg(not(feature = "gltf"))]
198                return Err(Error::FeatureMissing("gltf".to_string()));
199
200                #[cfg(feature = "gltf")]
201                gltf::deserialize_gltf(raw_assets, &path)
202            }
203            "obj" => {
204                #[cfg(not(feature = "obj"))]
205                return Err(Error::FeatureMissing("obj".to_string()));
206
207                #[cfg(feature = "obj")]
208                obj::deserialize_obj(raw_assets, &path)
209            }
210            "stl" => {
211                #[cfg(not(feature = "stl"))]
212                return Err(Error::FeatureMissing("stl".to_string()));
213
214                #[cfg(feature = "stl")]
215                stl::deserialize_stl(raw_assets, &path)
216            }
217            "pcd" => {
218                #[cfg(not(feature = "pcd"))]
219                return Err(Error::FeatureMissing("pcd".to_string()));
220
221                #[cfg(feature = "pcd")]
222                pcd::deserialize_pcd(raw_assets, &path)
223            }
224            "3mf" => {
225                #[cfg(not(feature = "3mf"))]
226                return Err(Error::FeatureMissing("3mf".to_string()));
227
228                #[cfg(feature = "3mf")]
229                three_mf::deserialize_3mf(raw_assets, &path)
230            }
231            _ => Err(Error::FailedDeserialize(path.to_str().unwrap().to_string())),
232        }
233    }
234}
235
236impl Deserialize for crate::Model {
237    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
238        let scene = crate::Scene::deserialize(path, raw_assets)?;
239        Ok(scene.into())
240    }
241}
242
243impl Serialize for crate::Scene {
244    fn serialize(&self, path: impl AsRef<Path>) -> Result<RawAssets> {
245        let path = path.as_ref();
246        match path.extension().map(|e| e.to_str().unwrap()).unwrap_or("") {
247            "3mf" => {
248                #[cfg(not(feature = "3mf"))]
249                return Err(Error::FeatureMissing("3mf".to_string()));
250
251                #[cfg(feature = "3mf")]
252                {
253                    let bytes = three_mf::serialize_3mf(self)?;
254                    let mut raw_assets = RawAssets::new();
255                    raw_assets.insert(path, bytes);
256                    Ok(raw_assets)
257                }
258            }
259            _ => Err(Error::FailedSerialize(path.to_str().unwrap().to_string())),
260        }
261    }
262}
263
264impl Deserialize for crate::VoxelGrid {
265    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
266        let path = raw_assets.match_path(path.as_ref())?;
267        match path.extension().map(|e| e.to_str().unwrap()).unwrap_or("") {
268            "vol" => {
269                #[cfg(not(feature = "vol"))]
270                return Err(Error::FeatureMissing("vol".to_string()));
271
272                #[cfg(feature = "vol")]
273                vol::deserialize_vol(raw_assets, &path)
274            }
275            _ => Err(Error::FailedDeserialize(path.to_str().unwrap().to_string())),
276        }
277    }
278}
279
280impl Deserialize for crate::Texture3D {
281    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
282        let path = raw_assets.match_path(path.as_ref())?;
283        let voxel_grid = crate::VoxelGrid::deserialize(path, raw_assets)?;
284        Ok(voxel_grid.voxels)
285    }
286}
287
288impl Deserialize for crate::TriMesh {
289    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
290        let path = path.as_ref();
291        let model = crate::Model::deserialize(path, raw_assets)?;
292        model
293            .geometries
294            .into_iter()
295            .find_map(|p| {
296                if let Geometry::Triangles(mesh) = p.geometry {
297                    Some(mesh)
298                } else {
299                    None
300                }
301            })
302            .ok_or_else(|| {
303                Error::FailedConvertion(
304                    "a triangle mesh".to_owned(),
305                    path.to_str().unwrap().to_owned(),
306                )
307            })
308    }
309}
310
311impl Deserialize for crate::PointCloud {
312    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
313        let path = path.as_ref();
314        let model = crate::Model::deserialize(path, raw_assets)?;
315        model
316            .geometries
317            .into_iter()
318            .find_map(|p| {
319                if let Geometry::Points(point_cloud) = p.geometry {
320                    Some(point_cloud)
321                } else {
322                    None
323                }
324            })
325            .ok_or_else(|| {
326                Error::FailedConvertion(
327                    "a point cloud".to_owned(),
328                    path.to_str().unwrap().to_owned(),
329                )
330            })
331    }
332}
333
334fn get_dependencies(raw_assets: &RawAssets) -> Vec<PathBuf> {
335    #[allow(unused_mut)]
336    let mut dependencies = HashSet::new();
337    for (path, _) in raw_assets.iter() {
338        match path.extension().map(|e| e.to_str().unwrap()).unwrap_or("") {
339            "gltf" | "glb" => {
340                #[cfg(feature = "gltf")]
341                dependencies.extend(gltf::dependencies(raw_assets, path));
342            }
343            "obj" => {
344                #[cfg(feature = "obj")]
345                dependencies.extend(obj::dependencies_obj(raw_assets, path));
346            }
347            "mtl" => {
348                #[cfg(feature = "obj")]
349                dependencies.extend(obj::dependencies_mtl(raw_assets, path));
350            }
351            _ => {}
352        }
353    }
354    dependencies
355        .into_iter()
356        .filter(|d| !raw_assets.contains_key(d))
357        .collect()
358}
359
360fn is_data_url(path: &Path) -> bool {
361    path.to_str()
362        .map(|s| s.starts_with("data:"))
363        .unwrap_or(false)
364}
365
366#[allow(unused_variables)]
367fn parse_data_url(path: &str) -> Result<Vec<u8>> {
368    #[cfg(feature = "data-url")]
369    {
370        let url = data_url::DataUrl::process(path)
371            .map_err(|e| Error::FailedParsingDataUrl(path.to_string(), format!("{:?}", e)))?;
372        let (body, _) = url
373            .decode_to_vec()
374            .map_err(|e| Error::FailedParsingDataUrl(path.to_string(), format!("{:?}", e)))?;
375        Ok(body)
376    }
377    #[cfg(not(feature = "data-url"))]
378    Err(Error::FeatureMissing("data-url".to_string()))
379}