Implement loading material textures from gltf and rendering them

This commit is contained in:
SeanOMik 2023-10-17 22:04:25 -04:00
parent 02a0eea7b3
commit fd9f4bee2a
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
22 changed files with 891 additions and 74 deletions

14
Cargo.lock generated
View File

@ -1091,6 +1091,12 @@ dependencies = [
"hashbrown 0.14.0",
]
[[package]]
name = "infer"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
[[package]]
name = "inflections"
version = "1.1.1"
@ -1302,6 +1308,8 @@ dependencies = [
"glam",
"gltf",
"image",
"infer",
"mime",
"percent-encoding",
"thiserror",
"tracing",
@ -1373,6 +1381,12 @@ dependencies = [
"objc",
]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"

View File

@ -1124,6 +1124,12 @@ dependencies = [
"hashbrown 0.14.1",
]
[[package]]
name = "infer"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
[[package]]
name = "inflections"
version = "1.1.1"
@ -1341,6 +1347,8 @@ dependencies = [
"glam",
"gltf",
"image",
"infer",
"mime",
"percent-encoding",
"thiserror",
"tracing",
@ -1412,6 +1420,12 @@ dependencies = [
"objc",
]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"

File diff suppressed because one or more lines are too long

View File

@ -75,7 +75,8 @@ async fn main() {
let mut resman = world.get_resource_mut::<ResourceManager>().unwrap();
let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
let cube_model = resman.request::<Model>("assets/cube-embedded.gltf").unwrap();
//let cube_model = resman.request::<Model>("assets/cube-embedded.gltf").unwrap();
let cube_model = resman.request::<Model>("assets/cube-texture-embedded.gltf").unwrap();
drop(resman);
/* world.spawn((MeshComponent::new(
@ -132,18 +133,18 @@ async fn main() {
};
let jiggle_system = |world: &mut World| -> anyhow::Result<()> {
let keys = world.get_resource();
let keys = world.get_resource::<InputButtons<KeyCode>>();
if keys.is_none() {
return Ok(());
}
let keys = keys.unwrap();
let keys: Ref<InputButtons<KeyCode>> = keys.unwrap();
let speed = 0.001;
let speed = 0.01;
let rot_speed = 1.0;
let mut dir_x = 0.0;
let mut dir_y = 0.0;
let mut dir_z = 0.0;
let mut rot_x = 0.0;
let mut rot_y = 0.0;
@ -164,6 +165,14 @@ async fn main() {
dir_y -= speed;
}
if keys.is_pressed(KeyCode::E) {
dir_z += speed;
}
if keys.is_pressed(KeyCode::Q) {
dir_z -= speed;
}
if keys.is_pressed(KeyCode::Left) {
rot_y -= rot_speed;
}
@ -182,7 +191,7 @@ async fn main() {
drop(keys);
if dir_x == 0.0 && dir_y == 0.0 && rot_x == 0.0 && rot_y == 0.0 {
if dir_x == 0.0 && dir_y == 0.0 && dir_z == 0.0 && rot_x == 0.0 && rot_y == 0.0 {
return Ok(());
}
@ -197,6 +206,7 @@ async fn main() {
t.translation.x *= -1.0; */
t.translation.x += dir_x;
t.translation.y += dir_y;
t.translation.z += dir_z;
t.rotate_x(math::Angle::Degrees(rot_x));
t.rotate_y(math::Angle::Degrees(rot_y));
}

View File

@ -12,6 +12,9 @@ edict = "0.5.0"
glam = "0.24.1"
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness"] }
image = "0.24.7"
# not using custom matcher, or file type from file path
infer = { version = "0.15.0", default-features = false }
mime = "0.3.17"
percent-encoding = "2.3.0"
thiserror = "1.0.48"
tracing = "0.1.37"

View File

@ -15,3 +15,5 @@ pub use model::*;
pub mod material;
pub use material::*;
pub(crate) mod util;

View File

@ -2,7 +2,7 @@ use std::{fs::File, sync::Arc, io::Read};
use image::ImageError;
use crate::{resource_manager::ResourceStorage, texture::Texture, resource::Resource};
use crate::{resource_manager::ResourceStorage, texture::Texture, resource::Resource, ResourceManager};
use super::{LoaderError, ResourceLoader};
@ -14,9 +14,9 @@ impl From<ImageError> for LoaderError {
/// A struct that implements the `ResourceLoader` trait used for loading textures.
#[derive(Default)]
pub struct TextureLoader;
pub struct ImageLoader;
impl ResourceLoader for TextureLoader {
impl ResourceLoader for ImageLoader {
fn extensions(&self) -> &[&str] {
&[
// the extensions of these are the names of the formats
@ -26,11 +26,23 @@ impl ResourceLoader for TextureLoader {
"ff",
// pnm
"pnm", "pbm", "pgm", "ppm", "pam",
"pnm", "pbm", "pgm", "ppm",
]
}
fn load(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
fn mime_types(&self) -> &[&str] {
&[
"image/bmp", "image/vnd.ms-dds", "image/gif", "image/x-icon", "image/jpeg",
"image/png", "image/qoi", "image/tga", "image/tiff", "image/webp",
// no known mime for farbfeld
// pnm, pbm, pgm, ppm
"image/x-portable-anymap", "image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
]
}
fn load(&self, _resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
// check if the file is supported by this loader
if !self.does_support_file(path) {
return Err(LoaderError::UnsupportedExtension(path.to_string()));
@ -54,6 +66,20 @@ impl ResourceLoader for TextureLoader {
Ok(Arc::new(res))
}
fn load_bytes(&self, _resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
let image = image::load_from_memory(&bytes[offset..(length-offset)])
.map_err(|e| match e {
ImageError::IoError(e) => LoaderError::IoError(e),
_ => LoaderError::DecodingError(e.into()),
})?;
let texture = Texture {
image,
};
let res = Resource::with_data(&uuid::Uuid::new_v4().to_string(), texture);
Ok(Arc::new(res))
}
}
#[cfg(test)]
@ -68,20 +94,22 @@ mod tests {
#[test]
fn check_unsupport() {
let loader = TextureLoader::default();
let loader = ImageLoader::default();
assert_eq!(loader.does_support_file("test.gltf"), false);
}
/// Tests loading an image
#[test]
fn image_load() {
let loader = TextureLoader::default();
loader.load(&get_image("squiggles.png")).unwrap();
let mut manager = ResourceManager::new();
let loader = ImageLoader::default();
loader.load(&mut manager, &get_image("squiggles.png")).unwrap();
}
#[test]
fn image_load_unsupported() {
let loader = TextureLoader::default();
assert!(loader.load(&get_image("squiggles.gltf")).is_err());
let mut manager = ResourceManager::new();
let loader = ImageLoader::default();
assert!(loader.load(&mut manager, &get_image("squiggles.gltf")).is_err());
}
}

View File

@ -1,11 +1,11 @@
pub mod texture;
pub mod image;
pub mod model;
use std::{io, sync::Arc, path::Path, ffi::OsStr};
use thiserror::Error;
use crate::resource_manager::ResourceStorage;
use crate::{resource_manager::ResourceStorage, ResourceManager};
#[derive(Error, Debug)]
pub enum LoaderError {
@ -30,8 +30,12 @@ impl From<io::Error> for LoaderError {
}
pub trait ResourceLoader: Send + Sync {
/// Returns the extensions that this loader supports.
fn extensions(&self) -> &[&str];
/// Returns the mime types that this loader supports.
fn mime_types(&self) -> &[&str];
/// Returns true if this loader supports the file.
fn does_support_file(&self, path: &str) -> bool {
match Path::new(path).extension().and_then(OsStr::to_str) {
Some(ext) => {
@ -41,18 +45,26 @@ pub trait ResourceLoader: Send + Sync {
}
}
fn load(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
/// Returns true if this loader supports the mime type.
fn does_support_mime(&self, mime: &str) -> bool {
self.mime_types().contains(&mime)
}
/// Load a resource from a path.
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
/// Load a resource from bytes.
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
}
#[cfg(test)]
mod tests {
use super::{*, texture::TextureLoader};
use super::{*, image::ImageLoader};
/// Ensure that `does_support_file` works
#[test]
fn check_support() {
let loader = TextureLoader::default();
let loader = ImageLoader::default();
let extensions = loader.extensions();
let fake_paths: Vec<String> = extensions.iter().map(|e| format!("a.{}", e)).collect();
for path in fake_paths.iter() {

View File

@ -1,8 +1,9 @@
use std::sync::Arc;
use std::{sync::Arc, path::{Path, PathBuf}};
use base64::Engine;
use thiserror::Error;
use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, MeshIndices};
use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, MeshIndices, ResourceManager, util};
use tracing::debug;
@ -12,11 +13,25 @@ impl From<gltf::Error> for LoaderError {
}
}
#[derive(Error, Debug)]
enum ModelLoaderError {
#[error("The model ({0}) is missing the BIN section in the gltf file")]
MissingBin(String),
#[error("There was an error with decoding a uri defined in the model: '{0}'")]
UriDecodingError(util::UriReadError),
}
impl From<ModelLoaderError> for LoaderError {
fn from(value: ModelLoaderError) -> Self {
LoaderError::DecodingError(value.into())
}
}
#[derive(Default)]
pub struct ModelLoader;
impl ModelLoader {
fn parse_uri(uri: &str) -> Option<Vec<u8>> {
/* fn parse_uri(containing_path: &str, uri: &str) -> Option<Vec<u8>> {
let uri = uri.strip_prefix("data")?;
let (mime, data) = uri.split_once(",")?;
@ -26,11 +41,13 @@ impl ModelLoader {
};
if is_base64 {
base64::engine::general_purpose::STANDARD.decode(data).ok()
Some(base64::engine::general_purpose::STANDARD.decode(data).unwrap())
} else {
Some(data.as_bytes().to_vec())
let full_path = format!("{containing_path}/{data}");
let buf = std::fs::read(&full_path).unwrap();
Some(buf)
}
}
} */
fn process_node(&self, buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> {
let mut meshes = vec![];
@ -61,7 +78,7 @@ impl ModelLoader {
// read tex coords
if let Some(tex_coords) = reader.read_tex_coords(0) {
let tex_coords: Vec<glam::Vec2> = tex_coords.into_f32().map(|t| t.into()).collect(); // TODO: u16, u8
let tex_coords: Vec<glam::Vec2> = tex_coords.into_f32().map(|t| t.into()).collect();
new_mesh.add_attribute(MeshVertexAttribute::TexCoords, VertexAttributeData::Vec2(tex_coords));
}
@ -101,17 +118,27 @@ impl ResourceLoader for ModelLoader {
]
}
fn load(&self, path: &str) -> Result<std::sync::Arc<dyn crate::ResourceStorage>, crate::LoaderError> {
fn mime_types(&self) -> &[&str] {
&[]
}
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<std::sync::Arc<dyn crate::ResourceStorage>, crate::LoaderError> {
// check if the file is supported by this loader
if !self.does_support_file(path) {
return Err(LoaderError::UnsupportedExtension(path.to_string()));
}
let mut parent_path = PathBuf::from(path);
parent_path.pop();
let parent_path = parent_path.display().to_string();
let gltf = gltf::Gltf::open(path)?;
let buffers: Vec<Vec<u8>> = gltf.buffers().map(|b| match b.source() {
gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec()),
gltf::buffer::Source::Uri(uri) => ModelLoader::parse_uri(uri),
gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec())
.ok_or(ModelLoaderError::MissingBin(path.to_string())),
gltf::buffer::Source::Uri(uri) => util::gltf_read_buffer_uri(&parent_path, uri)
.map_err(|e| ModelLoaderError::UriDecodingError(e)),
}).flatten().collect();
// TODO: Read in multiple scenes
@ -119,7 +146,7 @@ impl ResourceLoader for ModelLoader {
// Load the materials
let materials: Vec<Material> = gltf.materials()
.map(|mat| Material::from(mat)).collect();
.map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect();
let meshes: Vec<Mesh> = scene.nodes()
.map(|node| self.process_node(&buffers, &materials, node))
@ -127,6 +154,10 @@ impl ResourceLoader for ModelLoader {
Ok(Arc::new(Resource::with_data(path, Model::new(meshes))))
}
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn crate::ResourceStorage>, LoaderError> {
todo!()
}
}
#[cfg(test)]
@ -142,10 +173,11 @@ mod tests {
#[test]
fn test_loading() {
let path = test_file_path("test-embedded.gltf");
let path = test_file_path("texture-embedded.gltf");
let mut manager = ResourceManager::new();
let loader = ModelLoader::default();
let model = loader.load(&path).unwrap();
let model = loader.load(&mut manager, &path).unwrap();
let model = Arc::downcast::<Resource<Model>>(model.as_arc_any()).unwrap();
let model = model.data.as_ref().unwrap();
assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh
@ -154,6 +186,7 @@ mod tests {
assert!(mesh.normals().unwrap().len() > 0);
assert!(mesh.tex_coords().unwrap().len() > 0);
assert!(mesh.indices.clone().unwrap().len() > 0);
assert!(mesh.material().base_color_texture.is_some());
let _mesh_mat = mesh.material(); // inner panic if material was not loaded
}
}

View File

@ -1,4 +1,6 @@
use crate::{Texture, ResHandle};
use std::{fs::File, io::{BufReader, Read}, collections::hash_map::DefaultHasher, hash::{Hash, Hasher}};
use crate::{Texture, ResHandle, ResourceManager, util};
/// PBR metallic roughness
#[derive(Clone, Debug, Default)]
@ -27,10 +29,10 @@ impl From<gltf::material::PbrMetallicRoughness<'_>> for PbrRoughness {
#[derive(Clone, Debug, Default)]
pub struct PbrGlossiness {
/// The rgba diffuse color of the material
pub diffuse_color: [f32; 4],
pub diffuse_color: glam::Vec4,
// The base color texture
// pub diffuse_texture // TODO
pub specular: [f32; 3],
pub specular: glam::Vec3,
/// The glossiness factor of the material.
/// From 0.0 (no glossiness) to 1.0 (full glossiness)
pub glossiness: f32,
@ -40,39 +42,201 @@ pub struct PbrGlossiness {
impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness {
fn from(value: gltf::material::PbrSpecularGlossiness) -> Self {
PbrGlossiness {
diffuse_color: value.diffuse_factor(),
specular: value.specular_factor(),
diffuse_color: value.diffuse_factor().into(),
specular: value.specular_factor().into(),
glossiness: value.glossiness_factor()
}
}
}
/// The alpha rendering mode of a material.
/// This is essentially a re-export of gltf::material::AlphaMode
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
pub enum AlphaMode {
/// The alpha value is ignored and the rendered output is fully opaque.
#[default]
Opaque = 1,
/// The rendered output is either fully opaque or fully transparent depending on
/// the alpha value and the specified alpha cutoff value.
Mask,
/// The alpha value is used, to determine the transparency of the rendered output.
/// The alpha cutoff value is ignored.
Blend,
}
impl From<gltf::material::AlphaMode> for AlphaMode {
fn from(value: gltf::material::AlphaMode) -> Self {
match value {
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
gltf::material::AlphaMode::Mask => AlphaMode::Mask,
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
}
}
}
#[derive(Clone, Default)]
pub struct Material {
pub shader_uuid: Option<u64>,
pub name: Option<String>,
pub double_sided: bool,
pub pbr_roughness: PbrRoughness,
pub pbr_glossiness: Option<PbrGlossiness>,
pub alpha_cutoff: Option<f32>,
pub alpha_mode: gltf::material::AlphaMode,
pub texture: Option<ResHandle<Texture>>,
//pub pbr_roughness: PbrRoughness,
/// The RGBA base color of the model. If a texture is supplied with `base_color_texture`, this value
/// will tint the texture. If a texture is not provided, this value would be the color of the Material.
pub base_color: glam::Vec4,
/// The metalness of the material
/// From 0.0 (non-metal) to 1.0 (metal)
pub metallic: f32,
/// The roughness of the material
/// From 0.0 (smooth) to 1.0 (rough)
pub roughness: f32,
/// The base color texture of the model.
pub base_color_texture: Option<ResHandle<Texture>>,
/// The metallic-roughness texture.
///
/// The metalness values are sampled from the B channel. The roughness values are sampled from
/// the G channel. These values are linear. If other channels are present (R or A), they are
/// ignored for metallic-roughness calculations.
pub metallic_roughness_texture: Option<ResHandle<Texture>>,
/// A set of parameter values that are used to define the specular-glossiness material model
/// from Physically-Based Rendering (PBR) methodology.
/// GLTF extension: [KHR_materials_pbrSpecularGlossiness](https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness)
pub pbr_glossiness: Option<PbrGlossiness>,
/// The optional alpha cutoff value of the material.
pub alpha_cutoff: Option<f32>,
/// The alpha rendering mode of the material. The material's alpha rendering
/// mode enumeration specifying the interpretation of the alpha value of the main
/// factor and texture.
///
/// * In `Opaque` mode (default) the alpha value is ignored
/// and the rendered output is fully opaque.
/// * In `Mask` mode, the rendered
/// output is either fully opaque or fully transparent depending on the alpha
/// value and the specified alpha cutoff value.
/// * In `Blend` mode, the alpha value is used to composite the source and
/// destination areas and the rendered output is combined with the background
/// using the normal painting operation (i.e. the Porter and Duff over
/// operator).
pub alpha_mode: AlphaMode,
//pub texture: Option<ResHandle<Texture>>,
}
impl From<gltf::Material<'_>> for Material {
fn from(value: gltf::Material) -> Self {
impl Material {
/// Get a uri's identifier
///
/// I'm not actually sure how identifiable this would be
fn uri_ident(gltf_rel_path: &str, uri: &str) -> String {
let mut hasher = DefaultHasher::new();
uri.hash(&mut hasher);
let hash = hasher.finish();
format!("{gltf_rel_path};{hash}")
}
fn source_ident(gltf_rel_path: &str, src: &gltf::image::Source) -> Option<String> {
match src {
gltf::image::Source::View { view, mime_type } => {
let buf = view.buffer();
let src = buf.source();
match src {
gltf::buffer::Source::Bin => None,
gltf::buffer::Source::Uri(uri) => {
Some(Material::uri_ident(gltf_rel_path, uri))
}
}
},
gltf::image::Source::Uri { uri, mime_type } => {
Some(Material::uri_ident(gltf_rel_path, uri))
},
}
}
fn read_source(resource_manager: &mut ResourceManager, gltf_rel_path: &str, src: gltf::image::Source) -> Result<Vec<u8>, util::UriReadError> {
// TODO: Don't copy sources
match src {
gltf::image::Source::View { view, mime_type } => {
let buf = view.buffer();
let src = buf.source();
let offset = view.offset();
let len = view.length();
match src {
gltf::buffer::Source::Bin => todo!("Read material source from gltf Bin"),
gltf::buffer::Source::Uri(uri) => {
util::gltf_read_buffer_uri(gltf_rel_path, uri)
.map(|mut buf| {
buf.drain(0..offset);
buf.truncate(len);
buf
})
}
}
},
gltf::image::Source::Uri { uri, mime_type } => {
util::gltf_read_buffer_uri(gltf_rel_path, uri)
},
}
}
fn load_texture(resource_manager: &mut ResourceManager, gltf_rel_path: &str, texture_info: gltf::texture::Info<'_>) -> ResHandle<Texture> {
// TODO: texture_info.tex_coord()
let tex = texture_info.texture();
let img = tex.source();
let src = img.source();
let buf = Material::read_source(resource_manager, gltf_rel_path, src).unwrap();
let buflen = buf.len();
let mime_type = infer::get(&buf).expect("Failed to get file type").mime_type();
resource_manager.load_bytes::<Texture>(&uuid::Uuid::new_v4().to_string(), mime_type,
buf, 0, buflen).unwrap()
}
/// Load the Material from a gltf::Material.
///
/// `gltf_rel_path`: The relative path of the gltf file,
/// e.g. gltf model path is "resource/models/player.gltf", the relative path would be "resource/models/"
pub fn from_gltf(resource_manager: &mut ResourceManager, gltf_rel_path: &str, gltf_mat: gltf::Material) -> Self {
let pbr_rough = gltf_mat.pbr_metallic_roughness();
let base_color = pbr_rough.base_color_factor().into();
let metallic = pbr_rough.metallic_factor();
let roughness = pbr_rough.roughness_factor();
let base_color_texture = pbr_rough.base_color_texture()
.map(|info| Material::load_texture(resource_manager, gltf_rel_path, info));
let metallic_roughness_texture = pbr_rough.metallic_roughness_texture()
.map(|info| Material::load_texture(resource_manager, gltf_rel_path, info));
/* let base_color_texture = if let Some(base_tex_info) = pbr_rough.base_color_texture() {
Some(Material::load_texture(resource_manager, gltf_rel_path, base_tex_info))
} else { None }; */
Material {
name: value.name()
name: gltf_mat.name()
.map(|s| s.to_string()),
double_sided: value.double_sided(),
pbr_roughness: value.pbr_metallic_roughness().into(),
pbr_glossiness: value.pbr_specular_glossiness()
double_sided: gltf_mat.double_sided(),
base_color,
metallic,
roughness,
pbr_glossiness: gltf_mat.pbr_specular_glossiness()
.map(|o| o.into()),
alpha_cutoff: value.alpha_cutoff(),
alpha_mode: value.alpha_mode(),
alpha_cutoff: gltf_mat.alpha_cutoff(),
alpha_mode: gltf_mat.alpha_mode().into(),
shader_uuid: None,
texture: None,
// TODO
base_color_texture,
metallic_roughness_texture,
}
}
}

View File

@ -10,6 +10,15 @@ pub enum MeshIndices {
U32(Vec<u32>),
}
impl MeshIndices {
pub fn len(&self) -> usize {
match self {
MeshIndices::U16(v) => v.len(),
MeshIndices::U32(v) => v.len(),
}
}
}
/* impl From<Vec<u8>> for MeshIndices {
fn from(value: Vec<u8>) -> Self {
MeshIndices::U8(value)

View File

@ -2,7 +2,7 @@ use std::{sync::Arc, collections::{HashMap, hash_map::DefaultHasher}, hash::{Has
use thiserror::Error;
use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, texture::TextureLoader, model::ModelLoader}};
use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, image::ImageLoader, model::ModelLoader}};
pub trait ResourceStorage: Send + Sync + Any + 'static {
fn as_any(&self) -> &dyn Any;
@ -31,6 +31,10 @@ pub enum RequestError {
Loader(LoaderError),
#[error("The file extension is unsupported: '{0}'")]
UnsupportedFileExtension(String),
#[error("The mimetype is unsupported: '{0}'")]
UnsupportedMime(String),
#[error("The identifier is not found: '{0}'")]
IdentNotFound(String),
}
impl From<LoaderError> for RequestError {
@ -39,16 +43,19 @@ impl From<LoaderError> for RequestError {
}
}
/// A struct that stores all Manager data. This is requried for sending
//struct ManagerStorage
pub struct ResourceManager {
resources: HashMap<String, Arc<dyn ResourceStorage>>,
loaders: Vec<Box<dyn ResourceLoader>>,
loaders: Vec<Arc<dyn ResourceLoader>>,
}
impl ResourceManager {
pub fn new() -> Self {
Self {
resources: HashMap::new(),
loaders: vec![ Box::new(TextureLoader::default()), Box::new(ModelLoader::default()) ],
loaders: vec![ Arc::new(ImageLoader::default()), Arc::new(ModelLoader::default()) ],
}
}
@ -65,10 +72,11 @@ impl ResourceManager {
.find(|l| l.does_support_file(path)) {
// Load the resource and store it
let res = loader.load(path)?;
let loader = Arc::clone(&loader); // stop borrowing from self
let res = loader.load(self, path)?;
self.resources.insert(path.to_string(), res.clone());
// convert Arc<dyn ResourceStorage> to Arc<Resource<T>
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
let res = res.as_arc_any();
let res = res.downcast::<Resource<T>>()
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
@ -80,6 +88,49 @@ impl ResourceManager {
}
}
}
/// Store bytes in the manager. If there is already an entry with the same identifier it will be updated.
///
/// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic.
///
/// Parameters:
/// * `ident` - The identifier to store along with these bytes. Make sure its unique to avoid overriding something.
/// * `bytes` - The bytes to store.
///
/// Returns: The `Arc` to the now stored resource
pub fn load_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<Resource<T>>, RequestError> {
if let Some(loader) = self.loaders.iter()
.find(|l| l.does_support_mime(mime_type)) {
let loader = loader.clone();
let res = loader.load_bytes(self, bytes, offset, length)?;
self.resources.insert(ident.to_string(), res.clone());
// code here...
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
let res = res.as_arc_any();
let res = res.downcast::<Resource<T>>()
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
Ok(res)
} else {
Err(RequestError::UnsupportedMime(mime_type.to_string()))
}
}
/// Requests bytes from the manager.
pub fn request_loaded_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str) -> Result<Arc<Resource<T>>, RequestError> {
match self.resources.get(&ident.to_string()) {
Some(res) => {
let res = res.clone().as_arc_any();
let res = res.downcast::<Resource<T>>().expect("Failure to downcast resource");
Ok(res)
},
None => {
Err(RequestError::IdentNotFound(ident.to_string()))
}
}
}
}
#[cfg(test)]

38
lyra-resource/src/util.rs Normal file
View File

@ -0,0 +1,38 @@
use base64::Engine;
use thiserror::Error;
use std::io;
#[derive(Error, Debug)]
pub enum UriReadError {
#[error("IOError: '{0}'")]
IoError(io::Error),
// From is implemented for this field in each loader module
#[error("Base64 decoding error: '{0}'")]
Base64Decode(base64::DecodeError),
#[error("Some data was missing from the uri")]
None
}
/// Read a buffer's uri string into a byte buffer.
///
/// * `containing_path`: The path of the containing folder of the buffers "parent",
/// the parent being where this buffer is defined in,
/// i.e. parent="resources/models/player.gltf", containing="reousrce/models"
pub(crate) fn gltf_read_buffer_uri(containing_path: &str, uri: &str) -> Result<Vec<u8>, UriReadError> {
let uri = uri.strip_prefix("data").ok_or(UriReadError::None)?;
let (mime, data) = uri.split_once(",").ok_or(UriReadError::None)?;
let (_mime, is_base64) = match mime.strip_suffix(";base64") {
Some(mime) => (mime, true),
None => (mime, false),
};
if is_base64 {
base64::engine::general_purpose::STANDARD.decode(data).map_err(|e| UriReadError::Base64Decode(e))
} else {
let full_path = format!("{containing_path}/{data}");
std::fs::read(&full_path).map_err(|e| UriReadError::IoError(e))
}
}

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,137 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.6.6",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"uvgrid",
"uri":"uvgrid.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":480,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":840,
"uri":"texture-sep.bin"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -21,6 +21,7 @@ use crate::ecs::components::transform::TransformComponent;
use super::camera::RenderCamera;
use super::desc_buf_lay::DescVertexBufferLayout;
use super::texture::RenderTexture;
use super::vertex::Vertex;
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
use lyra_resource::Mesh;
@ -443,15 +444,26 @@ impl BasicRenderer {
}
fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) {
let vertices = mesh.position().unwrap();
let positions = mesh.position().unwrap();
let tex_coords: Vec<glam::Vec2> = mesh.tex_coords().cloned()
.unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]);
debug!("Pos count: {}, tex coords count: {}", positions.len(), tex_coords.len());
assert!(positions.len() == tex_coords.len());
let vertex_inputs: Vec<Vertex> = std::iter::zip(positions, tex_coords.into_iter())
.map(|(v, t)| Vertex::new(v.clone(), t))
.collect();
println!("Vertex inputs: {:?}", vertex_inputs.as_slice());
let vertex_buffer = self.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(vertices.as_slice()),
contents: bytemuck::cast_slice(vertex_inputs.as_slice()),//vertex_combined.as_slice(),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST,
}
);
let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertices.len());
let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len());
let indices = match mesh.indices.as_ref() {
Some(indices) => {
@ -483,7 +495,7 @@ impl BasicRenderer {
fn create_mesh_buffers(&mut self, mesh: &Mesh, transform_indices: TransformBufferIndices) -> RenderBufferStorage {
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh);
let diffuse_bindgroup = if let Some(model_texture) = &mesh.material().texture {
let diffuse_bindgroup = if let Some(model_texture) = &mesh.material().base_color_texture {
let image = &model_texture.data.as_ref().unwrap().image;
let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap();

View File

@ -2,7 +2,7 @@
struct VertexInput {
@location(0) position: vec3<f32>,
//@location(1) tex_coords: vec2<f32>,
@location(1) tex_coords: vec2<f32>,
}
struct VertexOutput {
@ -25,7 +25,7 @@ fn vs_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coords = vec2<f32>(1.0, 1.0);
out.tex_coords = model.tex_coords;
out.clip_position = camera.view_proj * u_model_transform * vec4<f32>(model.position, 1.0);
return out;
}

View File

@ -1,31 +1,37 @@
use glam::Vec3;
use super::desc_buf_lay::DescVertexBufferLayout;
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
pub position: [f32; 3],
pub position: glam::Vec3,
pub tex_coords: glam::Vec2
//pub color: [f32; 3], // TODO: add color again
//pub tex_coords: [f32; 2]
}
impl Vertex {
pub fn new(position: glam::Vec3, tex_coords: glam::Vec2) -> Self {
Self {
position, tex_coords
}
}
}
impl DescVertexBufferLayout for Vertex {
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vec3>() as wgpu::BufferAddress,
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
format: wgpu::VertexFormat::Float32x3, // Vec3
},
/* wgpu::VertexAttribute {
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
} */
format: wgpu::VertexFormat::Float32x2, // Vec2
}
]
}
}