modelz/
lib.rs

1#![deny(clippy::all)]
2#![warn(clippy::pedantic)]
3#![warn(clippy::nursery)]
4#![warn(clippy::cargo)]
5#![expect(clippy::single_call_fn)]
6#![expect(clippy::exhaustive_enums)]
7#![expect(clippy::exhaustive_structs)]
8
9use std::path::{Path, PathBuf};
10
11#[cfg(feature = "gltf")]
12mod gltf;
13#[cfg(feature = "obj")]
14mod obj;
15#[cfg(feature = "stl")]
16mod ply;
17#[cfg(feature = "ply")]
18mod stl;
19
20pub struct Model3D {
21    /// All meshes the Model has.
22    ///
23    /// Some 3D Formats do not have multiple meshes and have just vertices, In this case there will be one Mesh with all the Vertices
24    pub meshes: Vec<Mesh>,
25    /// All Materials the Model has.
26    ///
27    /// Some 3D Formats do not support Materials/Textures, In this case the Vec will be empty
28    pub materials: Vec<Material>,
29
30    /// The format which was used to load the Model
31    pub format: ModelFormat,
32}
33
34impl Model3D {
35    /// Load an Full 3D Model from the Given File extension
36    ///
37    /// # Examples
38    ///
39    /// ```
40    /// use modelz::Model3D;
41    ///
42    /// let model = Model3D::load("model.gltf");
43    ///
44    ///  for mesh in model.meshes {
45    ///     println!("{}", mesh.name.unwrap());
46    ///     for vert in mesh.vertices {
47    ///        println!("{:?}", vert)
48    ///     }
49    /// }
50    /// ```
51    ///
52    /// # Errors
53    ///
54    /// Returns an Error is loading the Model was unsuccessful
55    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, ModelError> {
56        let format = get_format(&path)?;
57        Self::from_format(path, &format)
58    }
59
60    /// Load an Full 3D Model from the Given `ModelFormat`
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use modelz::Model3D;
66    ///
67    /// let model = Model3D::from_format("model.gltf", ModelFormat::GLTF);
68    ///
69    /// let model = Model3D::from_format("model", ModelFormat::GLTF);
70    /// ```
71    /// # Errors
72    ///
73    /// Returns an Error is loading the Model was unsuccessful
74    pub fn from_format<P: AsRef<Path>>(path: P, format: &ModelFormat) -> Result<Self, ModelError> {
75        match format {
76            #[cfg(feature = "obj")]
77            ModelFormat::OBJ => obj::load(path.as_ref()),
78            #[cfg(feature = "gltf")]
79            ModelFormat::GLTF => gltf::load(path.as_ref()),
80            #[cfg(feature = "stl")]
81            ModelFormat::STL => stl::load(path.as_ref()),
82            #[cfg(feature = "ply")]
83            ModelFormat::PLY => ply::load(path.as_ref()),
84        }
85    }
86}
87
88#[non_exhaustive]
89/// `ModelFormat` represents the 3D Format being used to Load an File
90pub enum ModelFormat {
91    #[cfg(feature = "obj")]
92    // Wavefront obj, .obj
93    OBJ,
94    #[cfg(feature = "gltf")]
95    // gltf 2.0, .gltf | .glb
96    GLTF,
97    #[cfg(feature = "stl")]
98    // STL .stl
99    STL,
100    #[cfg(feature = "ply")]
101    // Polygon File Format .ply
102    PLY,
103}
104
105#[derive(Debug)]
106#[non_exhaustive]
107pub enum ModelError {
108    // Format is not supported for you may have to enable it as a crate feature
109    UnknowFormat,
110    // Given file does not exist
111    FileNotExists,
112    // Failed to open file
113    OpenFile(String),
114    // Error while loading general 3D File
115    ModelParsing(String),
116    // Error loading Material
117    MaterialLoad(String),
118}
119
120fn get_format<P: AsRef<Path>>(path: &P) -> Result<ModelFormat, ModelError> {
121    let path = path.as_ref();
122    if !path.exists() {
123        return Err(ModelError::FileNotExists);
124    }
125
126    let extension = path
127        .extension()
128        .and_then(|ext| return ext.to_str())
129        .expect("Failed to get File extension");
130    match extension {
131        #[cfg(feature = "obj")]
132        "obj" => Ok(ModelFormat::OBJ),
133        #[cfg(feature = "gltf")]
134        "gltf" | "glb" => Ok(ModelFormat::GLTF),
135        #[cfg(feature = "stl")]
136        "stl" => Ok(ModelFormat::STL),
137        _ => Err(ModelError::UnknowFormat),
138    }
139}
140
141pub struct Mesh {
142    /// All the Vertices the Mesh has.
143    pub vertices: Vec<Vertex>,
144    /// All the Indices the Mesh has.
145    pub indices: Option<Indices>,
146    /// The Render Mode that should be used to Render the Mesh
147    pub mode: RenderMode,
148    /// The index from the Vec Materials Vec in `Model3D`
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// let material = model.materials[mesh.material_index];
154    /// ```
155    pub material_index: Option<usize>,
156    /// Name of the Mesh.
157    ///
158    /// Some File Formats do not support Mesh names, In this case this will be `None`
159    pub name: Option<String>,
160}
161
162#[non_exhaustive]
163pub struct Material {
164    /// The optional diffuse Texture
165    pub diffuse_texture: Option<Texture>,
166    /// The alpha rendering mode of the material.  The material's alpha rendering
167    /// mode enumeration specifying the interpretation of the alpha value of the main
168    /// factor and texture.
169    pub alpha_mode: AlphaMode,
170    ///  The Alpha cutoff value of the material.
171    pub alpha_cutoff: Option<f32>,
172    /// Specifies whether the material is double-sided.
173    ///
174    /// When disabled, back-face culling is enabled
175    /// When enabled, back-face culling is disabled
176    pub double_sided: bool,
177    /// The Base color of the Material.
178    ///
179    /// Usally used to mutiple the diffuse texture
180    ///
181    /// Some File Formats do not support Material names, In this case this will be `None`
182    /// # Examples
183    ///
184    /// ```
185    /// vec4 texture = texture(texture_diffuse, tex_coord) * material.base_color;
186    /// ```
187    pub base_color: Option<[f32; 4]>,
188    /// Name of the Material.
189    ///
190    /// Some File Formats do not support Material names, In this case this will be `None`
191    pub name: Option<String>,
192}
193
194pub struct Texture {
195    /// The image from the `image` crate, Which is loaded into RAM
196    pub image: Image,
197    /// Sampler which beining used on the Image
198    pub sampler: Sampler,
199    /// Name of the Texture.
200    ///
201    /// Some File Formats do not support Texture names, In this case this will be `None`
202    pub name: Option<String>,
203}
204
205pub enum Image {
206    Memory {
207        data: Vec<u8>,
208        mime_type: Option<String>,
209    },
210    Path {
211        path: PathBuf,
212        mime_type: Option<String>,
213    },
214}
215
216#[derive(Default)]
217pub struct Sampler {
218    pub mag_filter: Option<MagFilter>,
219    pub min_filter: Option<MinFilter>,
220    pub wrap_s: WrappingMode,
221    pub wrap_t: WrappingMode,
222    pub name: Option<String>,
223}
224
225/// Mag Filter
226///
227/// # Rendering
228///
229/// Vulkan: Corresponds to `vk::Filter`
230/// <https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkFilter.html>
231#[derive(Clone, Copy, Debug, Eq, PartialEq)]
232pub enum MagFilter {
233    /// Corresponds to `GL_NEAREST` or `vk::Filter::NEAREST`.
234    Nearest = 1,
235
236    /// Corresponds to `GL_LINEAR` or `vk::Filter::LINEAR`.
237    Linear,
238}
239
240/// Mag Filter
241///
242/// # Rendering
243///
244/// Vulkan: Corresponds to `vk::Filter` & `vk::SamplerMipmapMode`
245/// <https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkFilter.html>
246/// <https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSamplerMipmapMode.html>
247#[derive(Clone, Copy, Debug, Eq, PartialEq)]
248pub enum MinFilter {
249    /// Corresponds to `GL_NEAREST` or `vk::Filter::NEAREST`.
250    Nearest = 1,
251
252    /// Corresponds to `GL_LINEAR` or `vk::Filter::LINEAR`.
253    Linear,
254
255    /// Corresponds to `GL_NEAREST_MIPMAP_NEAREST` or (`vk::Filter::NEAREST`, `vk::SamplerMipmapMode::NEAREST`).
256    NearestMipmapNearest,
257
258    /// Corresponds to `GL_LINEAR_MIPMAP_NEAREST` or (`vk::Filter::LINEAR`, `vk::SamplerMipmapMode::NEAREST`).
259    LinearMipmapNearest,
260
261    /// Corresponds to `GL_NEAREST_MIPMAP_LINEAR` or (`vk::Filter::NEAREST`, `vk::SamplerMipmapMode::LINEAR`).
262    NearestMipmapLinear,
263
264    /// Corresponds to `GL_LINEAR_MIPMAP_LINEAR` or (`vk::Filter::LINEAR`, `vk::SamplerMipmapMode::LINEAR`).
265    LinearMipmapLinear,
266}
267
268/// Wrapping Mode
269///
270/// # Rendering
271///
272/// Vulkan: Corresponds to `vk::SamplerAddressMode`
273/// <https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSamplerAddressMode.html>
274#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
275pub enum WrappingMode {
276    /// Corresponds to `GL_CLAMP_TO_EDGE` or `vk::SamplerAddressMode::CLAMP_TO_EDGE`.
277    ClampToEdge = 1,
278
279    /// Corresponds to [`GL_MIRRORED_REPEAT`] or [`vk::SamplerAddressMode::MIRRORED_REPEAT`].
280    MirroredRepeat,
281
282    /// Corresponds to `GL_REPEAT` or `vk::SamplerAddressMode::REPEAT`.
283    #[default]
284    Repeat,
285}
286
287#[derive(Clone, Copy, Debug, Eq, PartialEq)]
288pub enum AlphaMode {
289    /// The alpha value is ignored and the rendered output is fully opaque.
290    Opaque = 1,
291
292    /// The rendered output is either fully opaque or fully transparent depending on
293    /// the alpha value and the specified alpha cutoff value.
294    Mask,
295
296    /// The alpha value is used, to determine the transparency of the rendered output.
297    /// The alpha cutoff value is ignored.
298    Blend,
299}
300
301/// The type of primitives to render.
302///
303/// # Rendering
304///
305/// Vulkan: Corresponds to `vk::PrimitiveTopology`
306/// <https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPrimitiveTopology.html>
307#[derive(Clone, Copy, Debug, Eq, PartialEq)]
308pub enum RenderMode {
309    /// Corresponds to `GL_POINTS` or `vk::PrimitiveTopology::POINT_LIST`.
310    Points = 1,
311
312    /// Corresponds to `GL_LINES` or `vk::PrimitiveTopology::LINE_LIST`.
313    Lines,
314
315    /// Corresponds to [`GL_LINE_LOOP`] or [`vk::PrimitiveTopology::LINE_LIST`].
316    LineLoop,
317
318    /// Corresponds to `GL_LINE_STRIP` or `vk::PrimitiveTopology::LINE_STRIP`.
319    LineStrip,
320
321    /// Corresponds to `GL_TRIANGLES` or `vk::PrimitiveTopology::TRIANGLE_LIST`.
322    Triangles,
323
324    /// Corresponds to `GL_TRIANGLE_STRIP`or `vk::PrimitiveTopology::TRIANGLE_STRIP`.
325    TriangleStrip,
326
327    /// Corresponds to `GL_TRIANGLE_FAN` or `vk::PrimitiveTopology::TRIANGLE_FAN`.
328    TriangleFan,
329}
330
331#[derive(Clone, Debug)]
332pub struct Vertex {
333    pub position: [f32; 3],
334    pub color: Option<[f32; 4]>, // rgba f32
335    pub tex_coord: Option<[f32; 2]>,
336    pub normal: Option<[f32; 3]>,
337}
338
339#[derive(Clone, Debug)]
340/// Indicies
341///
342/// # Rendering
343///
344/// Vulkan: Corresponds to `vk::IndexType`
345/// <https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkIndexType.html>
346pub enum Indices {
347    U8(Vec<u8>),
348    U16(Vec<u16>),
349    U32(Vec<u32>),
350}