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///
67/// Deserialize a single file from raw bytes.
68///
69/// If the file depends on other files, use [RawAssets::insert] to insert the bytes for each of them in [RawAssets] before deserializing.
70///
71pub fn deserialize<T: Deserialize>(bytes: Vec<u8>) -> crate::Result<T> {
72    let mut assets = RawAssets::new();
73    assets.insert("", bytes);
74    assets.deserialize("")
75}
76
77///
78/// Loads and deserialize a single file. If the file depends on other files, those files are also loaded.
79///
80#[cfg(not(target_arch = "wasm32"))]
81pub fn load_and_deserialize<T: Deserialize>(path: impl AsRef<std::path::Path>) -> crate::Result<T> {
82    load(&[&path])?.deserialize(path)
83}
84
85///
86/// Async loads and deserialize a single file. If the file depends on other files, those files are also loaded.
87///
88pub async fn load_and_deserialize_async<T: Deserialize>(
89    path: impl AsRef<std::path::Path>,
90) -> crate::Result<T> {
91    load_async(&[&path]).await?.deserialize(path)
92}
93
94///
95/// Save and serialize a single file.
96///
97#[cfg(not(target_arch = "wasm32"))]
98pub fn serialize_and_save<T: Serialize>(
99    path: impl AsRef<std::path::Path>,
100    data: T,
101) -> crate::Result<()> {
102    save(&data.serialize(path)?)
103}
104
105///
106/// Implemented for assets that can be deserialized after being loaded (see also [load] and [RawAssets::deserialize]).
107///
108pub trait Deserialize: Sized {
109    ///
110    /// See [RawAssets::deserialize].
111    ///
112    fn deserialize(
113        path: impl AsRef<std::path::Path>,
114        raw_assets: &mut RawAssets,
115    ) -> crate::Result<Self>;
116}
117
118///
119/// Implemented for assets that can be serialized before being saved (see also [save]).
120///
121pub trait Serialize: Sized {
122    ///
123    /// 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]).
124    /// The path given as input is the path to the main raw asset.
125    ///
126    fn serialize(&self, path: impl AsRef<std::path::Path>) -> crate::Result<RawAssets>;
127}
128
129use crate::{Error, Geometry, Result};
130use std::collections::HashSet;
131use std::path::{Path, PathBuf};
132
133impl Deserialize for crate::Texture2D {
134    fn deserialize(path: impl AsRef<std::path::Path>, raw_assets: &mut RawAssets) -> Result<Self> {
135        let path = raw_assets.match_path(path.as_ref())?;
136        let extension = path
137            .extension()
138            .map(|e| e.to_str().unwrap())
139            .unwrap_or("image")
140            .to_string();
141        #[allow(unused_variables)]
142        let bytes = raw_assets.get(&path)?;
143
144        if "svg" == extension {
145            // to satisfy the compiler during wasm compile
146            #[cfg(not(feature = "svg"))]
147            return Err(Error::FeatureMissing("svg".to_string()));
148
149            #[cfg(feature = "svg")]
150            img::deserialize_svg(path, bytes)
151        } else {
152            #[cfg(not(feature = "image"))]
153            return Err(Error::FeatureMissing(extension));
154
155            #[cfg(feature = "image")]
156            img::deserialize_img(path, bytes)
157        }
158    }
159}
160
161impl Serialize for crate::Texture2D {
162    fn serialize(&self, path: impl AsRef<Path>) -> Result<RawAssets> {
163        let path = path.as_ref();
164
165        #[cfg(not(feature = "image"))]
166        return Err(Error::FeatureMissing(
167            path.extension()
168                .map(|e| e.to_str().unwrap())
169                .unwrap_or("image")
170                .to_string(),
171        ));
172
173        #[cfg(feature = "image")]
174        img::serialize_img(self, path)
175    }
176}
177
178impl Deserialize for crate::Scene {
179    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
180        let path = raw_assets.match_path(path.as_ref())?;
181        match path.extension().map(|e| e.to_str().unwrap()).unwrap_or("") {
182            "gltf" | "glb" => {
183                #[cfg(not(feature = "gltf"))]
184                return Err(Error::FeatureMissing("gltf".to_string()));
185
186                #[cfg(feature = "gltf")]
187                gltf::deserialize_gltf(raw_assets, &path)
188            }
189            "obj" => {
190                #[cfg(not(feature = "obj"))]
191                return Err(Error::FeatureMissing("obj".to_string()));
192
193                #[cfg(feature = "obj")]
194                obj::deserialize_obj(raw_assets, &path)
195            }
196            "stl" => {
197                #[cfg(not(feature = "stl"))]
198                return Err(Error::FeatureMissing("stl".to_string()));
199
200                #[cfg(feature = "stl")]
201                stl::deserialize_stl(raw_assets, &path)
202            }
203            "pcd" => {
204                #[cfg(not(feature = "pcd"))]
205                return Err(Error::FeatureMissing("pcd".to_string()));
206
207                #[cfg(feature = "pcd")]
208                pcd::deserialize_pcd(raw_assets, &path)
209            }
210            _ => Err(Error::FailedDeserialize(path.to_str().unwrap().to_string())),
211        }
212    }
213}
214
215impl Deserialize for crate::Model {
216    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
217        let scene = crate::Scene::deserialize(path, raw_assets)?;
218        Ok(scene.into())
219    }
220}
221
222impl Deserialize for crate::VoxelGrid {
223    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
224        let path = raw_assets.match_path(path.as_ref())?;
225        match path.extension().map(|e| e.to_str().unwrap()).unwrap_or("") {
226            "vol" => {
227                #[cfg(not(feature = "vol"))]
228                return Err(Error::FeatureMissing("vol".to_string()));
229
230                #[cfg(feature = "vol")]
231                vol::deserialize_vol(raw_assets, &path)
232            }
233            _ => Err(Error::FailedDeserialize(path.to_str().unwrap().to_string())),
234        }
235    }
236}
237
238impl Deserialize for crate::Texture3D {
239    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
240        let path = raw_assets.match_path(path.as_ref())?;
241        let voxel_grid = crate::VoxelGrid::deserialize(path, raw_assets)?;
242        Ok(voxel_grid.voxels)
243    }
244}
245
246impl Deserialize for crate::TriMesh {
247    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
248        let path = path.as_ref();
249        let model = crate::Model::deserialize(path, raw_assets)?;
250        model
251            .geometries
252            .into_iter()
253            .find_map(|p| {
254                if let Geometry::Triangles(mesh) = p.geometry {
255                    Some(mesh)
256                } else {
257                    None
258                }
259            })
260            .ok_or_else(|| {
261                Error::FailedConvertion(
262                    "a triangle mesh".to_owned(),
263                    path.to_str().unwrap().to_owned(),
264                )
265            })
266    }
267}
268
269impl Deserialize for crate::PointCloud {
270    fn deserialize(path: impl AsRef<Path>, raw_assets: &mut RawAssets) -> Result<Self> {
271        let path = path.as_ref();
272        let model = crate::Model::deserialize(path, raw_assets)?;
273        model
274            .geometries
275            .into_iter()
276            .find_map(|p| {
277                if let Geometry::Points(point_cloud) = p.geometry {
278                    Some(point_cloud)
279                } else {
280                    None
281                }
282            })
283            .ok_or_else(|| {
284                Error::FailedConvertion(
285                    "a point cloud".to_owned(),
286                    path.to_str().unwrap().to_owned(),
287                )
288            })
289    }
290}
291
292fn get_dependencies(raw_assets: &RawAssets) -> Vec<PathBuf> {
293    #[allow(unused_mut)]
294    let mut dependencies = HashSet::new();
295    for (path, _) in raw_assets.iter() {
296        match path.extension().map(|e| e.to_str().unwrap()).unwrap_or("") {
297            "gltf" | "glb" => {
298                #[cfg(feature = "gltf")]
299                dependencies.extend(gltf::dependencies(raw_assets, path));
300            }
301            "obj" => {
302                #[cfg(feature = "obj")]
303                dependencies.extend(obj::dependencies_obj(raw_assets, path));
304            }
305            "mtl" => {
306                #[cfg(feature = "obj")]
307                dependencies.extend(obj::dependencies_mtl(raw_assets, path));
308            }
309            _ => {}
310        }
311    }
312    dependencies
313        .into_iter()
314        .filter(|d| !raw_assets.contains_key(d))
315        .collect()
316}