three_d_asset/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2//#![warn(clippy::all)]
3#![warn(missing_docs)]
4
5//!
6//! A set of common assets that are useful when doing graphics, for example [TriMesh], [Texture2D] or [PbrMaterial].
7//! These assets can be loaded using the [io] module or constructed manually.
8//! When in memory, the assets can be for example be
9//! - visualised, for example using the [three-d](https://github.com/asny/three-d) crate or in a CPU ray tracer
10//! - imported into a rust-based game engine
11//! - edited and saved again
12//!
13
14pub mod prelude;
15
16mod camera;
17pub use camera::*;
18
19pub mod texture;
20pub use texture::*;
21
22pub mod material;
23pub use material::*;
24
25pub mod geometry;
26pub use geometry::*;
27
28pub mod volume;
29pub use volume::*;
30
31mod animation;
32pub use animation::*;
33
34///
35/// Representation of a set of objects as a scene graph.
36/// Specifically, a [Scene] contains a tree of [Node]s, where the nodes contain the [Geometry] data.
37/// A [Scene] can easily be converted into a [Model], if it is more desirable with a flat arrays instead of a tree structure.
38///
39/// To visualise the [Geometry] in the [Scene] correctly, it is necessary to traverse the scene from the root (the [Scene]) to the leaves
40/// and along the way calculate a transformation.
41/// For each node containing [Geometry], the [Geometry] should be visualised with the calculated transformation applied.
42///
43#[derive(Debug, Clone)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45pub struct Scene {
46    /// The name. Might not be anything meaningful.
47    pub name: String,
48    /// Children nodes.
49    pub children: Vec<Node>,
50    /// A list of materials used in this scene. The materials are referenced by index in the relevant nodes.
51    pub materials: Vec<PbrMaterial>,
52}
53
54impl Default for Scene {
55    fn default() -> Self {
56        Self {
57            name: "scene".to_owned(),
58            children: Vec::new(),
59            materials: Vec::new(),
60        }
61    }
62}
63
64///
65/// A node in a [Scene] graph. Each node may contain a set of children nodes, hence the whole [Scene] representaion has a tree structure.
66///
67/// Each node may also contain a transformation, animations, geometry and an index to the [Scene::materials].
68///
69#[derive(Debug, Clone)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71pub struct Node {
72    /// The name. Might not be anything meaningful.
73    pub name: String,
74    /// Children [Node]s.
75    pub children: Vec<Node>,
76    /// A transformation that should be applied to all [Geometry] referenced by this and all children nodes.
77    pub transformation: Mat4,
78    /// Optional animation applied to this node and all of its children.
79    /// A transformation should be computed for a specific time and then multiplied together with [Node::transformation].
80    pub animations: Vec<(Option<String>, KeyFrames)>,
81    /// Optional geometry for this node.
82    pub geometry: Option<Geometry>,
83    /// Optional index into [Scene::materials], indicating which material should be applied to geometry below this node in the tree.
84    pub material_index: Option<usize>,
85}
86
87impl Default for Node {
88    fn default() -> Self {
89        Self {
90            name: "node".to_owned(),
91            children: Vec::new(),
92            transformation: Mat4::identity(),
93            animations: Vec::new(),
94            geometry: None,
95            material_index: None,
96        }
97    }
98}
99
100///
101/// A [Model] contain the same data as a [Scene], it's just stored in flat arrays instead of in a tree structure.
102/// You can convert from a [Scene] to a [Model], but not the other way, because the tree structure is lost in the conversion.
103///
104#[derive(Debug, Clone)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub struct Model {
107    /// The name. Might not be anything meaningful.
108    pub name: String,
109    /// A list of geometries for this model.
110    pub geometries: Vec<Primitive>,
111    /// A list of materials for this model
112    pub materials: Vec<PbrMaterial>,
113}
114
115///
116/// A part of a [Model] containing exactly one [Geometry], an optional reference to a material and information necessary to calculate the transformation that
117/// should be applied to the geometry.
118///
119#[derive(Debug, Clone)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121pub struct Primitive {
122    /// The name. Might not be anything meaningful.
123    pub name: String,
124    /// A transformation that should be applied to the [Primitive::geometry].
125    pub transformation: Mat4,
126    /// Optional animation applied to the [Primitive::geometry].
127    /// A transformation should be computed for a specific time and then multiplied together with [Node::transformation].
128    pub animations: Vec<KeyFrameAnimation>,
129    /// The geometry of this primitive.
130    pub geometry: Geometry,
131    /// Optional index into [Model::materials], indicating which material should be applied to [Primitive::geometry].
132    pub material_index: Option<usize>,
133}
134
135impl std::ops::Deref for Primitive {
136    type Target = Geometry;
137    fn deref(&self) -> &Self::Target {
138        &self.geometry
139    }
140}
141
142impl std::ops::DerefMut for Primitive {
143    fn deref_mut(&mut self) -> &mut Self::Target {
144        &mut self.geometry
145    }
146}
147
148impl std::convert::From<Scene> for Model {
149    fn from(scene: Scene) -> Self {
150        let mut geometries = Vec::new();
151        for child in scene.children {
152            visit(child, Vec::new(), Mat4::identity(), &mut geometries);
153        }
154        Self {
155            name: scene.name,
156            materials: scene.materials,
157            geometries,
158        }
159    }
160}
161
162fn visit(
163    node: Node,
164    mut animations: Vec<KeyFrameAnimation>,
165    transformation: Mat4,
166    geometries: &mut Vec<Primitive>,
167) {
168    let mut transformation = transformation * node.transformation;
169    if !node.animations.is_empty() {
170        for (animation_name, key_frames) in node.animations {
171            if let Some(i) = animations.iter().position(|a| a.name == animation_name) {
172                animations[i]
173                    .key_frames
174                    .push((transformation, std::sync::Arc::new(key_frames)));
175            } else {
176                animations.push(KeyFrameAnimation {
177                    name: animation_name,
178                    key_frames: vec![(transformation, std::sync::Arc::new(key_frames))],
179                });
180            }
181        }
182        transformation = Mat4::identity();
183    };
184    if let Some(geometry) = node.geometry {
185        geometries.push(Primitive {
186            name: node.name.clone(),
187            transformation,
188            animations: animations.clone(),
189            geometry,
190            material_index: node.material_index,
191        });
192    }
193    for child in node.children {
194        visit(child, animations.clone(), transformation, geometries);
195    }
196}
197
198pub mod io;
199
200/// A result for this crate.
201pub type Result<T> = std::result::Result<T, Error>;
202
203use thiserror::Error;
204///
205/// Error from this crate.
206///
207#[derive(Error, Debug)]
208#[allow(missing_docs)]
209pub enum Error {
210    #[error("{0} buffer length must be {1}, actual length is {2}")]
211    InvalidBufferLength(String, usize, usize),
212    #[error("the number of indices must be divisable by 3, actual count is {0}")]
213    InvalidNumberOfIndices(usize),
214    #[error("the max index {0} must be less than the number of vertices {1}")]
215    InvalidIndices(usize, usize),
216    #[error("the transformation matrix cannot be inverted and is therefore invalid")]
217    FailedInvertingTransformationMatrix,
218    #[cfg(feature = "image")]
219    #[error("error while parsing an image file")]
220    Image(#[from] image::ImageError),
221
222    #[cfg(feature = "svg")]
223    #[error("error while parsing svg file")]
224    Svg(#[from] resvg::usvg::Error),
225
226    #[cfg(feature = "obj")]
227    #[error("error while parsing an .obj file")]
228    Obj(#[from] wavefront_obj::ParseError),
229
230    #[cfg(feature = "pcd")]
231    #[error("error while parsing an .pcd file")]
232    Pcd(#[from] pcd_rs::Error),
233
234    #[cfg(any(not(target_arch = "wasm32"), feature = "stl"))]
235    #[error("io error")]
236    IO(#[from] std::io::Error),
237    #[cfg(feature = "gltf")]
238    #[error("error while parsing a .gltf file")]
239    Gltf(#[from] ::gltf::Error),
240    #[cfg(feature = "gltf")]
241    #[error("the .gltf file contain corrupt buffer data")]
242    GltfCorruptData,
243    #[cfg(feature = "gltf")]
244    #[error("the .gltf file contain missing buffer data")]
245    GltfMissingData,
246    #[error("the .vol file contain wrong data size")]
247    VolCorruptData,
248    #[cfg(not(target_arch = "wasm32"))]
249    #[error("error while loading the file {0}: {1}")]
250    FailedLoading(String, std::io::Error),
251    #[cfg(feature = "reqwest")]
252    #[error("error while loading the url {0}: {1}")]
253    FailedLoadingUrlWithReqwest(String, reqwest::Error),
254    #[cfg(feature = "reqwest")]
255    #[error("error while loading the url {0}: {1}")]
256    FailedLoadingUrl(String, String),
257    #[cfg(feature = "reqwest")]
258    #[error("error while parsing the url {0}")]
259    FailedParsingUrl(String),
260    #[cfg(feature = "data-url")]
261    #[error("error while parsing data-url {0}: {1}")]
262    FailedParsingDataUrl(String, String),
263    #[error("tried to use {0} which was not loaded or otherwise added to the raw assets")]
264    NotLoaded(String),
265    #[error("the feature {0} is needed")]
266    FeatureMissing(String),
267    #[error("failed to deserialize the file {0}")]
268    FailedDeserialize(String),
269    #[error("failed to serialize the file {0}")]
270    FailedSerialize(String),
271    #[error("failed to find {0} in the file {1}")]
272    FailedConvertion(String, String),
273}