From 38e7b543c0ee6d44f921c416d8c7895ec7f5aff0 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 21 Sep 2023 14:22:46 -0400 Subject: [PATCH 01/14] Start working on gltf --- Cargo.lock | 85 +++++++++++++++++++++++++++ lyra-resource/Cargo.toml | 1 + lyra-resource/src/lib.rs | 3 + lyra-resource/src/loader/mod.rs | 35 +++++++++-- lyra-resource/src/loader/model.rs | 29 +++++++++ lyra-resource/src/loader/texture.rs | 24 +------- lyra-resource/src/model.rs | 18 ++++++ lyra-resource/src/resource_manager.rs | 2 +- 8 files changed, 170 insertions(+), 27 deletions(-) create mode 100644 lyra-resource/src/loader/model.rs create mode 100644 lyra-resource/src/model.rs diff --git a/Cargo.lock b/Cargo.lock index 8b637df..6a482f6 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,6 +297,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bit-set" version = "0.5.3" @@ -890,6 +896,44 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gltf" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2dcfb6dd7a66f9eb3d181a29dcfb22d146b0bcdc2e1ed1713cbf03939a88ea" +dependencies = [ + "base64", + "byteorder", + "gltf-json", + "image", + "lazy_static", + "urlencoding", +] + +[[package]] +name = "gltf-derive" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cbcea5dd47e7ad4e9ee6f040384fcd7204bbf671aa4f9e7ca7dfc9bfa1de20" +dependencies = [ + "inflections", + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "gltf-json" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5b810806b78dde4b71a95cc0e6fdcab34c4c617da3574df166f9987be97d03" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "gpu-alloc" version = "0.5.4" @@ -1041,6 +1085,12 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + [[package]] name = "instant" version = "0.1.12" @@ -1241,6 +1291,7 @@ name = "lyra-resource" version = "0.0.1" dependencies = [ "anyhow", + "gltf", "image", "thiserror", "uuid", @@ -1963,6 +2014,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1994,6 +2051,28 @@ version = "1.0.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" +[[package]] +name = "serde_derive" +version = "1.0.179" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2371,6 +2450,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "uuid" version = "1.4.1" diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index 0207327..6166616 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] anyhow = "1.0.75" +gltf = "1.3.0" image = "0.24.7" thiserror = "1.0.48" uuid = { version = "1.4.1", features = ["v4"] } diff --git a/lyra-resource/src/lib.rs b/lyra-resource/src/lib.rs index 347aa03..d1a4deb 100644 --- a/lyra-resource/src/lib.rs +++ b/lyra-resource/src/lib.rs @@ -9,3 +9,6 @@ pub use texture::*; pub mod loader; pub use loader::*; + +pub mod model; +pub use model::*; diff --git a/lyra-resource/src/loader/mod.rs b/lyra-resource/src/loader/mod.rs index 78e5e23..fb0b7eb 100644 --- a/lyra-resource/src/loader/mod.rs +++ b/lyra-resource/src/loader/mod.rs @@ -1,6 +1,7 @@ pub mod texture; +pub mod model; -use std::{io, sync::Arc, fs::File}; +use std::{io, sync::Arc, fs::File, path::Path, ffi::OsStr}; use thiserror::Error; @@ -15,7 +16,7 @@ pub enum LoaderError { UnsupportedExtension(String), #[error("IOError: '{0}'")] - IOError(io::Error), + IoError(io::Error), // From is implemented for this field in each loader module #[error("Decoding error: '{0}'")] @@ -24,12 +25,38 @@ pub enum LoaderError { impl From for LoaderError { fn from(value: io::Error) -> Self { - LoaderError::IOError(value) + LoaderError::IoError(value) } } pub trait ResourceLoader: Send + Sync { fn extensions(&self) -> &[&str]; - fn does_support_file(&self, path: &str) -> bool; + + fn does_support_file(&self, path: &str) -> bool { + match Path::new(path).extension().and_then(OsStr::to_str) { + Some(ext) => { + self.extensions().contains(&ext) + }, + _ => false, + } + } + fn load(&self, path: &str) -> Result, LoaderError>; +} + + +#[cfg(test)] +mod tests { + use super::{*, texture::TextureLoader}; + + /// Ensure that `does_support_file` works + #[test] + fn check_support() { + let loader = TextureLoader::default(); + let extensions = loader.extensions(); + let fake_paths: Vec = extensions.iter().map(|e| format!("a.{}", e)).collect(); + for path in fake_paths.iter() { + assert!(loader.does_support_file(&path)); + } + } } \ No newline at end of file diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs new file mode 100644 index 0000000..83197fc --- /dev/null +++ b/lyra-resource/src/loader/model.rs @@ -0,0 +1,29 @@ +use crate::{ResourceLoader, LoaderError}; + +impl From for LoaderError { + fn from(value: gltf::Error) -> Self { + LoaderError::DecodingError(value.into()) + } +} + +#[derive(Default)] +pub struct ModelLoader; + +impl ResourceLoader for ModelLoader { + fn extensions(&self) -> &[&str] { + &[ + "gltf" + ] + } + + fn load(&self, path: &str) -> Result, 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 gltf = gltf::Gltf::open(path)?; + + todo!() + } +} \ No newline at end of file diff --git a/lyra-resource/src/loader/texture.rs b/lyra-resource/src/loader/texture.rs index c781ffa..c77ed80 100644 --- a/lyra-resource/src/loader/texture.rs +++ b/lyra-resource/src/loader/texture.rs @@ -8,7 +8,7 @@ use super::{LoaderError, ResourceLoader}; impl From for LoaderError { fn from(value: ImageError) -> Self { - LoaderError::DecodingError(anyhow::Error::from(value)) + LoaderError::DecodingError(value.into()) } } @@ -30,15 +30,6 @@ impl ResourceLoader for TextureLoader { ] } - fn does_support_file(&self, path: &str) -> bool { - match Path::new(path).extension().and_then(OsStr::to_str) { - Some(ext) => { - self.extensions().contains(&ext) - }, - _ => false, - } - } - fn load(&self, path: &str) -> Result, LoaderError> { // check if the file is supported by this loader if !self.does_support_file(path) { @@ -53,7 +44,7 @@ impl ResourceLoader for TextureLoader { // load the image and construct Resource let image = image::load_from_memory(&buf) .map_err(|e| match e { - ImageError::IoError(e) => LoaderError::IOError(e), + ImageError::IoError(e) => LoaderError::IoError(e), _ => LoaderError::DecodingError(e.into()), })?; let texture = Texture { @@ -75,17 +66,6 @@ mod tests { format!("{manifest}/test_files/img/{path}") } - /// Ensure that `does_support_file` works - #[test] - fn check_support() { - let loader = TextureLoader::default(); - let extensions = loader.extensions(); - let fake_paths: Vec = extensions.iter().map(|e| format!("a.{}", e)).collect(); - for path in fake_paths.iter() { - assert!(loader.does_support_file(&path)); - } - } - #[test] fn check_unsupport() { let loader = TextureLoader::default(); diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs new file mode 100644 index 0000000..f8c0344 --- /dev/null +++ b/lyra-resource/src/model.rs @@ -0,0 +1,18 @@ + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct Vertex { + pub position: [f32; 3], + pub tex_coords: [f32; 2] +} + +#[derive(Clone)] +pub struct Mesh { + pub vertices: Vec, + pub indices: Option>, +} + +pub struct Model { + pub mesh: Mesh, + //pub material +} \ No newline at end of file diff --git a/lyra-resource/src/resource_manager.rs b/lyra-resource/src/resource_manager.rs index 09b699e..03853d5 100644 --- a/lyra-resource/src/resource_manager.rs +++ b/lyra-resource/src/resource_manager.rs @@ -125,7 +125,7 @@ mod tests { assert!( match err { // make sure the error is NotFound - RequestError::Loader(LoaderError::IOError(e)) if e.kind() == io::ErrorKind::NotFound => true, + RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true, _ => false } ); From dddf6123c4ac29bbe93d6eee9aa31c400514fe39 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 21 Sep 2023 17:27:21 -0400 Subject: [PATCH 02/14] Continue working on gltf loader --- lyra-resource/src/loader/model.rs | 48 ++++++++++++++++++++++++++++++- lyra-resource/src/model.rs | 2 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index 83197fc..9b2bc92 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -1,4 +1,4 @@ -use crate::{ResourceLoader, LoaderError}; +use crate::{ResourceLoader, LoaderError, Mesh}; impl From for LoaderError { fn from(value: gltf::Error) -> Self { @@ -9,6 +9,44 @@ impl From for LoaderError { #[derive(Default)] pub struct ModelLoader; +impl ModelLoader { + fn process_node(&self, views: Vec>, node: gltf::Node<'_>) -> Vec { + let mut meshes = vec![]; + if let Some(mesh) = node.mesh() { + for prim in mesh.primitives() { + let pos_accessor = prim.get(&gltf::Semantic::Positions).unwrap(); + let mut new_mesh = Mesh::default(); + + assert_eq!(pos_accessor.dimensions(), gltf::accessor::Dimensions::Vec3); // TODO: dont do this + + let view = pos_accessor.view().unwrap(); // TODO: handle sparse Accessor + let stride = view.stride().unwrap_or(0); // if stride is None, its tightly packed + let offset = view.offset(); + let buffer = view.buffer(); + let buf_len = buffer.length(); + + let view = views.iter().next().unwrap(); + //gltf.views() + + //vertices.data_type() == gltf:: + //new_mesh.vertices = vertices.; + + if let Some(indicies) = prim.indices() { + + } + } + //meshes.push(mesh); + } + + for child in node.children() { + let mut child_meshes = self.process_node(node); + meshes.append(&mut child_meshes); + } + + todo!() + } +} + impl ResourceLoader for ModelLoader { fn extensions(&self) -> &[&str] { &[ @@ -24,6 +62,14 @@ impl ResourceLoader for ModelLoader { let gltf = gltf::Gltf::open(path)?; + let buffers: Vec> = gltf.buffers().collect(); + + // TODO: Read in multiple scenes + let scene = gltf.scenes().next().unwrap(); + for node in scene.nodes() { + + } + todo!() } } \ No newline at end of file diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index f8c0344..1f8d40d 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -6,7 +6,7 @@ pub struct Vertex { pub tex_coords: [f32; 2] } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Mesh { pub vertices: Vec, pub indices: Option>, From e76ca1ec5048247a0e417d226c9094553f23052d Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 21 Sep 2023 23:11:09 -0400 Subject: [PATCH 03/14] Write a very experimental gltf loader --- lyra-resource/Cargo.toml | 3 + lyra-resource/src/loader/model.rs | 101 +++++++++++---- lyra-resource/src/model.rs | 25 +++- lyra-resource/test_files/gltf/test-blobs.glb | Bin 0 -> 1936 bytes .../test_files/gltf/test-embedded.gltf | 121 ++++++++++++++++++ lyra-resource/test_files/gltf/test-sep.bin | Bin 0 -> 840 bytes lyra-resource/test_files/gltf/test-sep.gltf | 121 ++++++++++++++++++ 7 files changed, 342 insertions(+), 29 deletions(-) create mode 100644 lyra-resource/test_files/gltf/test-blobs.glb create mode 100644 lyra-resource/test_files/gltf/test-embedded.gltf create mode 100644 lyra-resource/test_files/gltf/test-sep.bin create mode 100644 lyra-resource/test_files/gltf/test-sep.gltf diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index 6166616..43b0d9c 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -7,7 +7,10 @@ edition = "2021" [dependencies] anyhow = "1.0.75" +base64 = "0.21.4" +glam = "0.24.1" gltf = "1.3.0" image = "0.24.7" +percent-encoding = "2.3.0" thiserror = "1.0.48" uuid = { version = "1.4.1", features = ["v4"] } diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index 9b2bc92..56eb5d8 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -1,4 +1,9 @@ -use crate::{ResourceLoader, LoaderError, Mesh}; +use std::sync::Arc; + +use base64::Engine; +use glam::Vec3; + +use crate::{ResourceLoader, LoaderError, Mesh, Vertex, Model}; impl From for LoaderError { fn from(value: gltf::Error) -> Self { @@ -10,40 +15,56 @@ impl From for LoaderError { pub struct ModelLoader; impl ModelLoader { - fn process_node(&self, views: Vec>, node: gltf::Node<'_>) -> Vec { + fn parse_uri(uri: &str) -> Option> { + let uri = uri.strip_prefix("data")?; + let (mime, data) = uri.split_once(",")?; + + 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).ok() + } else { + Some(data.as_bytes().to_vec()) + } + } + + fn process_node(&self, buffers: &Vec>, node: gltf::Node<'_>) -> Vec { let mut meshes = vec![]; if let Some(mesh) = node.mesh() { for prim in mesh.primitives() { let pos_accessor = prim.get(&gltf::Semantic::Positions).unwrap(); - let mut new_mesh = Mesh::default(); - - assert_eq!(pos_accessor.dimensions(), gltf::accessor::Dimensions::Vec3); // TODO: dont do this - - let view = pos_accessor.view().unwrap(); // TODO: handle sparse Accessor - let stride = view.stride().unwrap_or(0); // if stride is None, its tightly packed - let offset = view.offset(); - let buffer = view.buffer(); - let buf_len = buffer.length(); - - let view = views.iter().next().unwrap(); - //gltf.views() + //assert_eq!(pos_accessor.dimensions(), gltf::accessor::Dimensions::Vec3); // TODO: dont do this - //vertices.data_type() == gltf:: - //new_mesh.vertices = vertices.; + let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice())); + let pos: Vec = reader.read_positions() + .unwrap() + .map(|pos| Vec3::new(pos[0], pos[1], pos[2])) + .collect(); - if let Some(indicies) = prim.indices() { + let indices: Option> = reader.read_indices() + .map(|i| match i { + gltf::mesh::util::ReadIndices::U8(i) => i.map(|i| i as u32).collect(), + gltf::mesh::util::ReadIndices::U16(i) => i.map(|i| i as u32).collect(), + gltf::mesh::util::ReadIndices::U32(i) => i.collect(), + }); - } + let mut new_mesh = Mesh::default(); + new_mesh.vertices = pos.into_iter().map(|p| Vertex::new(p, glam::Vec2::new(0.0, 0.0))).collect(); + new_mesh.indices = indices; + + meshes.push(new_mesh); } - //meshes.push(mesh); } for child in node.children() { - let mut child_meshes = self.process_node(node); + let mut child_meshes = self.process_node(buffers, child); meshes.append(&mut child_meshes); } - todo!() + meshes } } @@ -62,14 +83,44 @@ impl ResourceLoader for ModelLoader { let gltf = gltf::Gltf::open(path)?; - let buffers: Vec> = gltf.buffers().collect(); + //let buffers: Vec> = gltf.buffers().collect(); + + let buffers: Vec> = 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), + }).flatten().collect(); // TODO: Read in multiple scenes let scene = gltf.scenes().next().unwrap(); - for node in scene.nodes() { - } + let meshes: Vec = scene.nodes() + .map(|node| self.process_node(&buffers, node)) + .flatten().collect(); - todo!() + Ok(Arc::new(Model::new(meshes))) + } +} + +#[cfg(test)] +mod tests { + use crate::ResourceLoader; + use super::*; + + fn test_file_path(path: &str) -> String { + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + format!("{manifest}/test_files/gltf/{path}") + } + + #[test] + fn test_loading() { + let path = test_file_path("test-embedded.gltf"); + + let loader = ModelLoader::default(); + let model = loader.load(&path).unwrap(); + let model = Arc::downcast::(model.as_arc_any()).unwrap(); + let mesh = &model.meshes[0]; + assert!(mesh.vertices.len() > 0); + assert!(mesh.indices.clone().unwrap().len() > 0); } } \ No newline at end of file diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index 1f8d40d..8684b61 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -2,17 +2,34 @@ #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct Vertex { - pub position: [f32; 3], - pub tex_coords: [f32; 2] + pub position: glam::Vec3, + pub tex_coords: glam::Vec2 +} + +impl Vertex { + pub fn new(position: glam::Vec3, tex_coords: glam::Vec2) -> Self { + Self { + position, + tex_coords, + } + } } #[derive(Clone, Default)] pub struct Mesh { pub vertices: Vec, - pub indices: Option>, + pub indices: Option>, } pub struct Model { - pub mesh: Mesh, + pub meshes: Vec, //pub material +} + +impl Model { + pub fn new(meshes: Vec) -> Self { + Self { + meshes, + } + } } \ No newline at end of file diff --git a/lyra-resource/test_files/gltf/test-blobs.glb b/lyra-resource/test_files/gltf/test-blobs.glb new file mode 100644 index 0000000000000000000000000000000000000000..8f2d6fbe11ab6838d18ca2ca9921d34e589cb353 GIT binary patch literal 1936 zcmb7ES#R1v5S|=uk~Te(Uax4MU1}RbfQQnACMs=$D?m}CC~AyZh$XP2wMiQxB>#l| zqx7eBX6+3QVDyN!?SNPPzAtudM>ct+XN$UAhEX#0l3CIoqA{qf zB!Pm$riIFNrOR%NWeeDwZ2D>%vO6qPxC1wI&l95^hQe(RLpqMwPpesL)vUUlN^9q^ zYFUl1zjRhMG*-77`&)agY~s{iuj4utlA8<*W5#m8wu7d?7xDDk!)}+01DF4nppHK{ z^F8i`t&20fqYc9>Xw3149zF&$PiTMerVI@~)Uk_Kee}`L(B(Zc*_F?2awUavo2)R%ev_QOj! zo|e^__ziNVfyn+Q-!F>hZ V!4sH=W!#tHIjq481YAQ}g#R-Q*3tj~ literal 0 HcmV?d00001 diff --git a/lyra-resource/test_files/gltf/test-embedded.gltf b/lyra-resource/test_files/gltf/test-embedded.gltf new file mode 100644 index 0000000..5d42356 --- /dev/null +++ b/lyra-resource/test_files/gltf/test-embedded.gltf @@ -0,0 +1,121 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v3.6.5", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"Cube", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "TEXCOORD_0":1, + "NORMAL":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "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 + } + ], + "buffers":[ + { + "byteLength":840, + "uri":"data:application/octet-stream;base64,AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AAAAPgAAAD8AAMA+AABAPwAAwD4AAEA/AAAgPwAAAAAAACA/AACAPwAAYD8AAIA+AAAAPgAAgD4AAMA+AAAAAAAAwD4AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAQAOABQAAQAUAAcACgAGABIACgASABYAFwATAAwAFwAMABAADwADAAkADwAJABUABQACAAgABQAIAAsAEQANAAAAEQAAAAQA" + } + ] +} diff --git a/lyra-resource/test_files/gltf/test-sep.bin b/lyra-resource/test_files/gltf/test-sep.bin new file mode 100644 index 0000000000000000000000000000000000000000..9322438cd45aec0d8a555a9fbe532cd726c3f498 GIT binary patch literal 840 zcma)&Sq{QL3`5P{vbSu%oP=_e9%XJ;jz->eB9&1=M9SE)g9%8XQQuuc<}r@u`ZwMT zGQM%oz#rqTfxo!>zpzR7cn)*UeFjJPy^&;{ Date: Fri, 22 Sep 2023 12:42:36 -0400 Subject: [PATCH 04/14] Add MeshVertexAttribute instead of directly storing positions --- lyra-resource/src/loader/model.rs | 56 ++++++++++++++++-------- lyra-resource/src/model.rs | 71 ++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 18 deletions(-) diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index 56eb5d8..1952c81 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use base64::Engine; use glam::Vec3; -use crate::{ResourceLoader, LoaderError, Mesh, Vertex, Model}; +use crate::{ResourceLoader, LoaderError, Mesh, Vertex, Model, MeshVertexAttribute, VertexAttributeData}; impl From for LoaderError { fn from(value: gltf::Error) -> Self { @@ -35,25 +35,44 @@ impl ModelLoader { let mut meshes = vec![]; if let Some(mesh) = node.mesh() { for prim in mesh.primitives() { - let pos_accessor = prim.get(&gltf::Semantic::Positions).unwrap(); - //assert_eq!(pos_accessor.dimensions(), gltf::accessor::Dimensions::Vec3); // TODO: dont do this - let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice())); - let pos: Vec = reader.read_positions() - .unwrap() - .map(|pos| Vec3::new(pos[0], pos[1], pos[2])) - .collect(); - let indices: Option> = reader.read_indices() - .map(|i| match i { + let mut new_mesh = Mesh::default(); + + // read the positions + if let Some(pos) = reader.read_positions() { + let pos: Vec = pos.map(|t| t.into()).collect(); + new_mesh.add_attribute(MeshVertexAttribute::Position, VertexAttributeData::Vec3(pos)); + } + + // read the normals + if let Some(norms) = reader.read_normals() { + let norms: Vec = norms.map(|t| t.into()).collect(); + new_mesh.add_attribute(MeshVertexAttribute::Normals, VertexAttributeData::Vec3(norms)); + } + + // read the tangents + if let Some(tangents) = reader.read_tangents() { + let tangents: Vec = tangents.map(|t| t.into()).collect(); + new_mesh.add_attribute(MeshVertexAttribute::Tangents, VertexAttributeData::Vec4(tangents)); + } + + // read tex coords + if let Some(tex_coords) = reader.read_tex_coords(0) { + let tex_coords: Vec = tex_coords.into_f32().map(|t| t.into()).collect(); // TODO: u16, u8 + new_mesh.add_attribute(MeshVertexAttribute::TexCoords, VertexAttributeData::Vec2(tex_coords)); + } + + // read the indices + if let Some(indices) = reader.read_indices() { + let indices: Vec = match indices { gltf::mesh::util::ReadIndices::U8(i) => i.map(|i| i as u32).collect(), gltf::mesh::util::ReadIndices::U16(i) => i.map(|i| i as u32).collect(), gltf::mesh::util::ReadIndices::U32(i) => i.collect(), - }); + }; - let mut new_mesh = Mesh::default(); - new_mesh.vertices = pos.into_iter().map(|p| Vertex::new(p, glam::Vec2::new(0.0, 0.0))).collect(); - new_mesh.indices = indices; + new_mesh.indices = Some(indices); + } meshes.push(new_mesh); } @@ -82,8 +101,6 @@ impl ResourceLoader for ModelLoader { } let gltf = gltf::Gltf::open(path)?; - - //let buffers: Vec> = gltf.buffers().collect(); let buffers: Vec> = gltf.buffers().map(|b| match b.source() { gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec()), @@ -93,6 +110,8 @@ impl ResourceLoader for ModelLoader { // TODO: Read in multiple scenes let scene = gltf.scenes().next().unwrap(); + // TODO: materials + let meshes: Vec = scene.nodes() .map(|node| self.process_node(&buffers, node)) .flatten().collect(); @@ -119,8 +138,11 @@ mod tests { let loader = ModelLoader::default(); let model = loader.load(&path).unwrap(); let model = Arc::downcast::(model.as_arc_any()).unwrap(); + assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh let mesh = &model.meshes[0]; - assert!(mesh.vertices.len() > 0); + assert!(mesh.position().unwrap().len() > 0); + assert!(mesh.normals().unwrap().len() > 0); + assert!(mesh.tex_coords().unwrap().len() > 0); assert!(mesh.indices.clone().unwrap().len() > 0); } } \ No newline at end of file diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index 8684b61..a7c7a10 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + #[repr(C)] #[derive(Copy, Clone, Debug)] @@ -15,12 +17,79 @@ impl Vertex { } } +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +pub enum VertexAttributeData { + Vec2(Vec), + Vec3(Vec), + Vec4(Vec), +} + +impl VertexAttributeData { + /// Take self as a list of Vec2's + pub fn as_vec2(&self) -> &Vec { + match self { + VertexAttributeData::Vec2(v) => v, + _ => panic!("Attempt to get {self:?} as `Vec2`"), + } + } + + pub fn as_vec3(&self) -> &Vec { + match self { + VertexAttributeData::Vec3(v) => v, + _ => panic!("Attempt to get {self:?} as `Vec3`"), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum MeshVertexAttribute { + Position, + Normals, + Tangents, + // Colors, // TODO: Store data in VertexAttributeData + Joints, // TODO: Animation + TexCoords, + Weights, // TODO: Animation + MorphTargets, // TODO: Animation + Other(String), +} + #[derive(Clone, Default)] pub struct Mesh { - pub vertices: Vec, + pub attributes: HashMap, pub indices: Option>, } +impl Mesh { + pub fn add_attribute(&mut self, attribute: MeshVertexAttribute, data: VertexAttributeData) { + self.attributes.insert(attribute, data); + } + + /// Try to get the position attribute of the Mesh + pub fn position(&self) -> Option<&Vec> { + self.attributes.get(&MeshVertexAttribute::Position) + .map(|p| p.as_vec3()) + } + + pub fn add_position(&mut self, pos: Vec) { + self.attributes.insert(MeshVertexAttribute::Position, VertexAttributeData::Vec3(pos)); + } + + /// Try to get the normals attribute of the Mesh + pub fn normals(&self) -> Option<&Vec> { + self.attributes.get(&MeshVertexAttribute::Normals) + .map(|p| p.as_vec3()) + } + + /// Try to get the texture coordinates attribute of the Mesh + pub fn tex_coords(&self) -> Option<&Vec> { + self.attributes.get(&MeshVertexAttribute::TexCoords) + .map(|p| p.as_vec2()) + } +} + +#[derive(Clone, Default)] pub struct Model { pub meshes: Vec, //pub material From dabc051b5866d313d7a3850ed6a5c614dfa4bb4f Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Tue, 26 Sep 2023 17:14:38 -0400 Subject: [PATCH 05/14] Add ModelComponent, add model loader to resource manager's default loaders --- Cargo.lock | 12 ++++++++- lyra-resource/Cargo.toml | 1 + lyra-resource/src/loader/mod.rs | 2 +- lyra-resource/src/loader/model.rs | 5 ++-- lyra-resource/src/loader/texture.rs | 2 +- lyra-resource/src/model.rs | 21 ++------------- lyra-resource/src/resource_manager.rs | 7 ++--- src/ecs/components/mod.rs | 1 + src/ecs/components/model.rs | 39 +++++++++++++++++++++++++++ 9 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 src/ecs/components/model.rs diff --git a/Cargo.lock b/Cargo.lock index 6a482f6..179240b 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,6 +303,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + [[package]] name = "bit-set" version = "0.5.3" @@ -902,7 +908,7 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad2dcfb6dd7a66f9eb3d181a29dcfb22d146b0bcdc2e1ed1713cbf03939a88ea" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "gltf-json", "image", @@ -1291,8 +1297,12 @@ name = "lyra-resource" version = "0.0.1" dependencies = [ "anyhow", + "base64 0.21.4", + "edict", + "glam", "gltf", "image", + "percent-encoding", "thiserror", "uuid", ] diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index 43b0d9c..25cea1b 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] anyhow = "1.0.75" base64 = "0.21.4" +edict = "0.5.0" glam = "0.24.1" gltf = "1.3.0" image = "0.24.7" diff --git a/lyra-resource/src/loader/mod.rs b/lyra-resource/src/loader/mod.rs index fb0b7eb..1b26c7b 100644 --- a/lyra-resource/src/loader/mod.rs +++ b/lyra-resource/src/loader/mod.rs @@ -1,7 +1,7 @@ pub mod texture; pub mod model; -use std::{io, sync::Arc, fs::File, path::Path, ffi::OsStr}; +use std::{io, sync::Arc, path::Path, ffi::OsStr}; use thiserror::Error; diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index 1952c81..a85f12e 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -1,9 +1,8 @@ use std::sync::Arc; use base64::Engine; -use glam::Vec3; -use crate::{ResourceLoader, LoaderError, Mesh, Vertex, Model, MeshVertexAttribute, VertexAttributeData}; +use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource}; impl From for LoaderError { fn from(value: gltf::Error) -> Self { @@ -116,7 +115,7 @@ impl ResourceLoader for ModelLoader { .map(|node| self.process_node(&buffers, node)) .flatten().collect(); - Ok(Arc::new(Model::new(meshes))) + Ok(Arc::new(Resource::with_data(path, Model::new(meshes)))) } } diff --git a/lyra-resource/src/loader/texture.rs b/lyra-resource/src/loader/texture.rs index c77ed80..66e5c17 100644 --- a/lyra-resource/src/loader/texture.rs +++ b/lyra-resource/src/loader/texture.rs @@ -1,4 +1,4 @@ -use std::{fs::File, sync::Arc, path::Path, ffi::OsStr, io::Read}; +use std::{fs::File, sync::Arc, io::Read}; use image::ImageError; diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index a7c7a10..f181efc 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -1,22 +1,5 @@ use std::collections::HashMap; - -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct Vertex { - pub position: glam::Vec3, - pub tex_coords: glam::Vec2 -} - -impl Vertex { - pub fn new(position: glam::Vec3, tex_coords: glam::Vec2) -> Self { - Self { - position, - tex_coords, - } - } -} - #[repr(C)] #[derive(Clone, Debug, PartialEq)] pub enum VertexAttributeData { @@ -47,7 +30,7 @@ pub enum MeshVertexAttribute { Position, Normals, Tangents, - // Colors, // TODO: Store data in VertexAttributeData + Colors, // TODO: Figure out best way to store color data Joints, // TODO: Animation TexCoords, Weights, // TODO: Animation @@ -55,7 +38,7 @@ pub enum MeshVertexAttribute { Other(String), } -#[derive(Clone, Default)] +#[derive(Clone, Default, edict::Component)] pub struct Mesh { pub attributes: HashMap, pub indices: Option>, diff --git a/lyra-resource/src/resource_manager.rs b/lyra-resource/src/resource_manager.rs index 03853d5..0204dee 100644 --- a/lyra-resource/src/resource_manager.rs +++ b/lyra-resource/src/resource_manager.rs @@ -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}}; +use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, texture::TextureLoader, model::ModelLoader}}; pub trait ResourceStorage: Send + Sync + Any + 'static { fn as_any(&self) -> &dyn Any; @@ -48,7 +48,7 @@ impl ResourceManager { pub fn new() -> Self { Self { resources: HashMap::new(), - loaders: vec![ Box::new(TextureLoader::default()) ], + loaders: vec![ Box::new(TextureLoader::default()), Box::new(ModelLoader::default()) ], } } @@ -70,7 +70,8 @@ impl ResourceManager { // convert Arc to Arc let res = res.as_arc_any(); - let res = res.downcast::>().expect("Failure to downcast resource"); + let res = res.downcast::>() + .expect("Failure to downcast resource! Does the loader return an `Arc>`?"); Ok(res) } else { diff --git a/src/ecs/components/mod.rs b/src/ecs/components/mod.rs index 93b9a2b..4a474dd 100755 --- a/src/ecs/components/mod.rs +++ b/src/ecs/components/mod.rs @@ -1,3 +1,4 @@ pub mod mesh; +pub mod model; pub mod transform; pub mod camera; \ No newline at end of file diff --git a/src/ecs/components/model.rs b/src/ecs/components/model.rs new file mode 100644 index 0000000..5f68790 --- /dev/null +++ b/src/ecs/components/model.rs @@ -0,0 +1,39 @@ +use crate::assets::{Model, Resource}; + +use std::sync::Arc; + +#[derive(Clone, edict::Component)] +pub struct ModelComponent(pub Arc>); + +impl From>> for ModelComponent { + fn from(value: Arc>) -> Self { + ModelComponent(value) + } +} + +/* impl From> for ModelComponent { + +} */ + +impl std::ops::Deref for ModelComponent { + type Target = Arc>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for ModelComponent { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/* impl ModelComponent { + pub fn new(model, material: Material) -> Self { + Self { + mesh, + material + } + } +} */ \ No newline at end of file From 792596078d3800c3d2867a609fa89b3977f5a8a1 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 29 Sep 2023 13:00:33 -0400 Subject: [PATCH 06/14] Create a material type for loading materials --- lyra-resource/Cargo.toml | 2 +- lyra-resource/src/lib.rs | 3 ++ lyra-resource/src/loader/model.rs | 20 +++++++++--- lyra-resource/src/material.rs | 51 +++++++++++++++++++++++++++++++ lyra-resource/src/model.rs | 9 ++++++ lyra-resource/src/resource.rs | 3 ++ 6 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 lyra-resource/src/material.rs diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index 25cea1b..f5a7799 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -10,7 +10,7 @@ anyhow = "1.0.75" base64 = "0.21.4" edict = "0.5.0" glam = "0.24.1" -gltf = "1.3.0" +gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness"] } image = "0.24.7" percent-encoding = "2.3.0" thiserror = "1.0.48" diff --git a/lyra-resource/src/lib.rs b/lyra-resource/src/lib.rs index d1a4deb..ccdbdc1 100644 --- a/lyra-resource/src/lib.rs +++ b/lyra-resource/src/lib.rs @@ -12,3 +12,6 @@ pub use loader::*; pub mod model; pub use model::*; + +pub mod material; +pub use material::*; \ No newline at end of file diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index a85f12e..a5427e6 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use base64::Engine; -use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource}; +use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, PbrRoughness}; impl From for LoaderError { fn from(value: gltf::Error) -> Self { @@ -30,7 +30,7 @@ impl ModelLoader { } } - fn process_node(&self, buffers: &Vec>, node: gltf::Node<'_>) -> Vec { + fn process_node(&self, buffers: &Vec>, materials: &Vec, node: gltf::Node<'_>) -> Vec { let mut meshes = vec![]; if let Some(mesh) = node.mesh() { for prim in mesh.primitives() { @@ -73,12 +73,14 @@ impl ModelLoader { new_mesh.indices = Some(indices); } + prim.material(). + meshes.push(new_mesh); } } for child in node.children() { - let mut child_meshes = self.process_node(buffers, child); + let mut child_meshes = self.process_node(buffers, materials, child); meshes.append(&mut child_meshes); } @@ -109,10 +111,18 @@ impl ResourceLoader for ModelLoader { // TODO: Read in multiple scenes let scene = gltf.scenes().next().unwrap(); - // TODO: materials + // Load the materials + let materials: Vec = gltf.materials() + .map(|mat| Material { + double_sided: mat.double_sided(), + name: mat.name().map(|s| s.to_string()), + shader_uuid: None, + pbr: Some(PbrRoughness::from(mat.pbr_metallic_roughness())), + texture: None + }).collect(); let meshes: Vec = scene.nodes() - .map(|node| self.process_node(&buffers, node)) + .map(|node| self.process_node(&buffers, &materials, node)) .flatten().collect(); Ok(Arc::new(Resource::with_data(path, Model::new(meshes)))) diff --git a/lyra-resource/src/material.rs b/lyra-resource/src/material.rs new file mode 100644 index 0000000..0a566c4 --- /dev/null +++ b/lyra-resource/src/material.rs @@ -0,0 +1,51 @@ +use crate::{Texture, ResHandle}; + +/// PBR metallic roughness +#[derive(Clone, Debug, Default)] +pub struct PbrRoughness { + /// The rgba base color of the PBR material + pub base_color: [f32; 4], + /// 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, + // TODO: base_color_texture and metallic_roughness_texture +} + +impl From> for PbrRoughness { + fn from(value: gltf::material::PbrMetallicRoughness) -> Self { + PbrRoughness { + base_color: value.base_color_factor(), + metallic: value.metallic_factor(), + roughness: value.roughness_factor(), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct PbrGlossiness { + /// The rgba diffuse color of the material + pub diffuse_color: [f32; 4], + // The base color texture + // pub diffuse_texture // TODO + pub specular: [f32; 3], + /// The glossiness factor of the material. + /// From 0.0 (no glossiness) to 1.0 (full glossiness) + pub glossiness: f32, + // pub glossiness_texture // TODO +} + +#[derive(Clone, Default)] +pub struct Material { + pub shader_uuid: Option, + pub name: Option, + pub double_sided: bool, + pub pbr_roughness: PbrRoughness, + pub pbr_glossiness: Option, + pub alpha_cutoff: Option, + pub alpha_mode: gltf::material::AlphaMode, + + pub texture: Option>, +} \ No newline at end of file diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index f181efc..104ab84 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use crate::{Material, ResHandle}; + #[repr(C)] #[derive(Clone, Debug, PartialEq)] pub enum VertexAttributeData { @@ -35,6 +37,8 @@ pub enum MeshVertexAttribute { TexCoords, Weights, // TODO: Animation MorphTargets, // TODO: Animation + /// Used during loading of the Mesh to process the materials taht it + MaterialRef, Other(String), } @@ -42,6 +46,7 @@ pub enum MeshVertexAttribute { pub struct Mesh { pub attributes: HashMap, pub indices: Option>, + material: Option>, } impl Mesh { @@ -70,6 +75,10 @@ impl Mesh { self.attributes.get(&MeshVertexAttribute::TexCoords) .map(|p| p.as_vec2()) } + + pub fn material(&self) -> ResHandle { + self.material.clone().expect("This mesh is missing a material!") + } } #[derive(Clone, Default)] diff --git a/lyra-resource/src/resource.rs b/lyra-resource/src/resource.rs index dd70bac..331d5f8 100644 --- a/lyra-resource/src/resource.rs +++ b/lyra-resource/src/resource.rs @@ -16,6 +16,9 @@ pub struct Resource { pub state: ResourceState, } +/// A helper type to make it easier to use resources +pub type ResHandle = Arc>; + impl Resource { /// Create the resource with data, its assumed the state is `Ready` pub fn with_data(path: &str, data: T) -> Self { From 9d6d51af8352f2659fda9773e51e5aed0bf71baa Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 29 Sep 2023 14:20:28 -0400 Subject: [PATCH 07/14] Load materials from gltf --- lyra-resource/src/loader/model.rs | 18 ++++++++---------- lyra-resource/src/material.rs | 27 +++++++++++++++++++++++++++ lyra-resource/src/model.rs | 8 ++++++-- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index a5427e6..3b878f2 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use base64::Engine; -use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, PbrRoughness}; +use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, ResHandle}; impl From for LoaderError { fn from(value: gltf::Error) -> Self { @@ -73,7 +73,9 @@ impl ModelLoader { new_mesh.indices = Some(indices); } - prim.material(). + let mat = materials.get(prim.material().index().unwrap()).unwrap(); + new_mesh.set_material(mat.clone()); + //prim.material(). meshes.push(new_mesh); } @@ -113,13 +115,7 @@ impl ResourceLoader for ModelLoader { // Load the materials let materials: Vec = gltf.materials() - .map(|mat| Material { - double_sided: mat.double_sided(), - name: mat.name().map(|s| s.to_string()), - shader_uuid: None, - pbr: Some(PbrRoughness::from(mat.pbr_metallic_roughness())), - texture: None - }).collect(); + .map(|mat| Material::from(mat)).collect(); let meshes: Vec = scene.nodes() .map(|node| self.process_node(&buffers, &materials, node)) @@ -146,12 +142,14 @@ mod tests { let loader = ModelLoader::default(); let model = loader.load(&path).unwrap(); - let model = Arc::downcast::(model.as_arc_any()).unwrap(); + let model = Arc::downcast::>(model.as_arc_any()).unwrap(); + let model = model.data.as_ref().unwrap(); assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh let mesh = &model.meshes[0]; assert!(mesh.position().unwrap().len() > 0); assert!(mesh.normals().unwrap().len() > 0); assert!(mesh.tex_coords().unwrap().len() > 0); assert!(mesh.indices.clone().unwrap().len() > 0); + let _mesh_mat = mesh.material(); // inner panic if material was not loaded } } \ No newline at end of file diff --git a/lyra-resource/src/material.rs b/lyra-resource/src/material.rs index 0a566c4..7ef227d 100644 --- a/lyra-resource/src/material.rs +++ b/lyra-resource/src/material.rs @@ -37,6 +37,16 @@ pub struct PbrGlossiness { // pub glossiness_texture // TODO } +impl From> for PbrGlossiness { + fn from(value: gltf::material::PbrSpecularGlossiness) -> Self { + PbrGlossiness { + diffuse_color: value.diffuse_factor(), + specular: value.specular_factor(), + glossiness: value.glossiness_factor() + } + } +} + #[derive(Clone, Default)] pub struct Material { pub shader_uuid: Option, @@ -48,4 +58,21 @@ pub struct Material { pub alpha_mode: gltf::material::AlphaMode, pub texture: Option>, +} + +impl From> for Material { + fn from(value: gltf::Material) -> Self { + Material { + name: value.name() + .map(|s| s.to_string()), + double_sided: value.double_sided(), + pbr_roughness: value.pbr_metallic_roughness().into(), + pbr_glossiness: value.pbr_specular_glossiness() + .map(|o| o.into()), + alpha_cutoff: value.alpha_cutoff(), + alpha_mode: value.alpha_mode(), + shader_uuid: None, + texture: None, + } + } } \ No newline at end of file diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index 104ab84..e83fe3d 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -46,7 +46,7 @@ pub enum MeshVertexAttribute { pub struct Mesh { pub attributes: HashMap, pub indices: Option>, - material: Option>, + material: Option, } impl Mesh { @@ -76,9 +76,13 @@ impl Mesh { .map(|p| p.as_vec2()) } - pub fn material(&self) -> ResHandle { + pub fn material(&self) -> Material { self.material.clone().expect("This mesh is missing a material!") } + + pub fn set_material(&mut self, val: Material) { + self.material = Some(val); + } } #[derive(Clone, Default)] From fdf1c4d33850857d67ad72e1c549a624ad8accc9 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 29 Sep 2023 14:46:08 -0400 Subject: [PATCH 08/14] Start implementing the new Model and Mesh types with the renderer --- src/ecs/components/mesh.rs | 6 +- src/ecs/components/model.rs | 14 ++-- src/render/render_job.rs | 11 +-- src/render/renderer.rs | 163 +++++++++++++++++++----------------- 4 files changed, 99 insertions(+), 95 deletions(-) diff --git a/src/ecs/components/mesh.rs b/src/ecs/components/mesh.rs index b169c63..6612ea6 100755 --- a/src/ecs/components/mesh.rs +++ b/src/ecs/components/mesh.rs @@ -1,18 +1,16 @@ use edict::Component; -use crate::render::{vertex::Vertex, mesh::Mesh, material::Material}; +use lyra_resource::Mesh; #[derive(Clone, Component)] pub struct MeshComponent { pub mesh: Mesh, - pub material: Material, } impl MeshComponent { - pub fn new(mesh: Mesh, material: Material) -> Self { + pub fn new(mesh: Mesh) -> Self { Self { mesh, - material } } } \ No newline at end of file diff --git a/src/ecs/components/model.rs b/src/ecs/components/model.rs index 5f68790..b50b3de 100644 --- a/src/ecs/components/model.rs +++ b/src/ecs/components/model.rs @@ -1,22 +1,22 @@ -use crate::assets::{Model, Resource}; +use lyra_resource::ResHandle; -use std::sync::Arc; +use crate::assets::Model; #[derive(Clone, edict::Component)] -pub struct ModelComponent(pub Arc>); +pub struct ModelComponent(pub ResHandle); -impl From>> for ModelComponent { - fn from(value: Arc>) -> Self { +impl From> for ModelComponent { + fn from(value: ResHandle) -> Self { ModelComponent(value) } } -/* impl From> for ModelComponent { +/* impl From for ModelComponent { } */ impl std::ops::Deref for ModelComponent { - type Target = Arc>; + type Target = ResHandle; fn deref(&self) -> &Self::Target { &self.0 diff --git a/src/render/render_job.rs b/src/render/render_job.rs index c331039..a7000aa 100755 --- a/src/render/render_job.rs +++ b/src/render/render_job.rs @@ -2,11 +2,11 @@ use edict::EntityId; use crate::math::Transform; -use super::{mesh::Mesh, material::Material}; +//use super::mesh::Mesh; +use lyra_resource::Mesh; pub struct RenderJob { mesh: Mesh, - material: Material, entity: EntityId, transform: Transform, @@ -14,10 +14,9 @@ pub struct RenderJob { } impl RenderJob { - pub fn new(mesh: Mesh, material: Material, entity: EntityId, transform: Transform, last_transform: Option) -> Self { + pub fn new(mesh: Mesh, entity: EntityId, transform: Transform, last_transform: Option) -> Self { Self { mesh, - material, entity, transform, last_transform, @@ -28,10 +27,6 @@ impl RenderJob { &self.mesh } - pub fn material(&self)-> &Material { - &self.material - } - pub fn entity(&self)-> EntityId { self.entity } diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 0d1aec1..ab01d2f 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -5,30 +5,25 @@ use std::num::NonZeroU64; use std::sync::Arc; use std::borrow::Cow; -use aligned_vec::AVec; -use async_std::sync::Mutex; -use async_trait::async_trait; - -use atomicell::{AtomicCell, RefMut}; use edict::query::EpochOf; use edict::{EntityId, Entities}; -use glam::Mat4; +use glam::Vec3; use tracing::{debug, warn}; -use wgpu::{BindGroup, BindGroupLayout, Limits, BufferBinding}; +use wgpu::{BindGroup, BindGroupLayout, Limits}; use wgpu::util::DeviceExt; use winit::window::Window; use crate::ecs::components::camera::CameraComponent; use crate::ecs::components::mesh::MeshComponent; +use crate::ecs::components::model::ModelComponent; use crate::ecs::components::transform::TransformComponent; -use crate::math::{Transform, Angle}; -use crate::resources; 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, mesh::Mesh}; +use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; + +use lyra_resource::Mesh; pub trait Renderer { fn prepare(&mut self, main_world: &mut edict::World); @@ -36,7 +31,7 @@ pub trait Renderer { fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize); fn surface_size(&self) -> winit::dpi::PhysicalSize; - fn add_render_pipeline(&mut self, shader_id: u32, pipeline: Arc); + fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc); } struct RenderBufferStorage { @@ -145,7 +140,7 @@ pub struct BasicRenderer { pub clear_color: wgpu::Color, - pub render_pipelines: HashMap>, + pub render_pipelines: HashMap>, pub render_jobs: VecDeque, buffer_storage: HashMap, // TODO: clean up left over buffers from deleted entities/components @@ -391,13 +386,13 @@ impl BasicRenderer { } // TODO: minimize how often model buffers are updated by checking if they changed - fn update_model_buffers(&mut self, entity: EntityId, model: &MeshComponent) { + fn update_mesh_buffers(&mut self, entity: EntityId, mesh: &Mesh) { if let Some(buffers) = self.buffer_storage.get_mut(&entity) { // check if the buffer sizes dont match. If they dont, completely remake the buffers - let vertices = &model.mesh.vertices; + let vertices = mesh.position().unwrap(); if buffers.buffer_vertex.count() != vertices.len() { drop(buffers); - let (vert, idx) = self.create_vertex_index_buffers(&model.mesh); + let (vert, idx) = self.create_vertex_index_buffers(mesh); // have to re-get buffers because of borrow checker let buffers = self.buffer_storage.get_mut(&entity).unwrap(); @@ -411,14 +406,14 @@ impl BasicRenderer { let vertex_buffer = buffers.buffer_vertex.buffer(); let vertices = vertices.as_slice(); // align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT) - let (_, vertices, _) = bytemuck::pod_align_to::(vertices); + let (_, vertices, _) = bytemuck::pod_align_to::(vertices); self.queue.write_buffer(&vertex_buffer, 0, bytemuck::cast_slice(&vertices)); // update the indices if they're given if let Some(index_buffer) = buffers.buffer_indices.as_ref() { let index_buffer = index_buffer.buffer(); - let indices = model.mesh.indices.as_ref().unwrap().as_slice(); - let (_, indices, _) = bytemuck::pod_align_to::(indices); + let indices = mesh.indices.as_ref().unwrap().as_slice(); + let (_, indices, _) = bytemuck::pod_align_to::(indices); // TODO: Don't force indicies into u32 self.queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(&indices)); } @@ -426,14 +421,15 @@ impl BasicRenderer { } fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option) { + let vertices = mesh.position().unwrap(); let vertex_buffer = self.device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(mesh.vertices.as_slice()), + contents: bytemuck::cast_slice(vertices.as_slice()), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST, } ); - let vertex_buffer = BufferStorage::new(vertex_buffer, 0, mesh.vertices.len()); + let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertices.len()); let buffer_indices = match mesh.indices.as_ref() { Some(indices) => { @@ -457,63 +453,66 @@ impl BasicRenderer { ( vertex_buffer, buffer_indices ) } - fn create_model_buffers(&mut self, model: &MeshComponent, transform_indices: TransformBufferIndices) -> RenderBufferStorage { - let mesh = &model.mesh; - + fn create_mesh_buffers(&mut self, mesh: &Mesh, transform_indices: TransformBufferIndices) -> RenderBufferStorage { let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh); - let model_texture = &model.material.texture; - let image = &model_texture.data.as_ref().unwrap().image; - let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap(); + let (diffuse_layout, diffuse_bindgroup) = if let Some(model_texture) = &mesh.material().texture { + let image = &model_texture.data.as_ref().unwrap().image; + let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap(); - let texture_bind_group_layout = - self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, + let texture_bind_group_layout = + self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - // This should match the filterable field of the - // corresponding Texture entry above. - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + // This should match the filterable field of the + // corresponding Texture entry above. + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); - let diffuse_bind_group = self.device.create_bind_group( - &wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(diffuse_texture.view()), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(diffuse_texture.sampler()), - } - ], - label: Some("diffuse_bind_group"), - } - ); + let diffuse_bind_group = self.device.create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(diffuse_texture.view()), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(diffuse_texture.sampler()), + } + ], + label: Some("diffuse_bind_group"), + } + ); + + (Some(texture_bind_group_layout), Some(diffuse_bind_group)) + } else { + (None, None) + }; RenderBufferStorage { buffer_vertex: vertex_buffer, buffer_indices, render_texture: None, - texture_layout: None, - texture_bindgroup: Some(diffuse_bind_group), + texture_layout: diffuse_layout, + texture_bindgroup: diffuse_bindgroup, transform_index: transform_indices } } @@ -561,9 +560,13 @@ impl Renderer for BasicRenderer { let mut alive_entities = HashSet::new(); - for (entity, model, model_epoch, transform) in main_world.query::<(Entities, &MeshComponent, EpochOf, &TransformComponent)>().iter() { + for (entity, model, model_epoch, transform) in main_world.query::<(Entities, &ModelComponent, EpochOf, &TransformComponent)>().iter() { + debug!("Collecting model things"); + let model = model.data.as_ref().unwrap().as_ref(); + let model_mesh = model.meshes.first().unwrap(); + // Create the render job and push it to the queue - let job = RenderJob::new(model.mesh.clone(), model.material.clone(), entity, transform.transform, None); + let job = RenderJob::new(model_mesh.clone(), entity, transform.transform, None); self.render_jobs.push_back(job); alive_entities.insert(entity); @@ -579,7 +582,7 @@ impl Renderer for BasicRenderer { entity, transform.transform.calculate_mat4()); // create the mesh's buffers - let buffers = self.create_model_buffers(model, indices); + let buffers = self.create_mesh_buffers(model_mesh, indices); self.buffer_storage.insert(entity, buffers); } else { // update entity transforms @@ -588,11 +591,15 @@ impl Renderer for BasicRenderer { // if the model was updated, update its buffers if model_epoch == last_epoch { - self.update_model_buffers(entity, model); + self.update_mesh_buffers(entity, model_mesh); } } } + for (entity, mesh, mesh_epoch, transform) in main_world.query::<(Entities, &MeshComponent, EpochOf, &TransformComponent)>().iter() { + debug!("TODO: Process MeshComponents"); // TODO: Process MeshComponents + } + // collect dead entities self.transform_buffers.tick(); @@ -642,9 +649,11 @@ impl Renderer for BasicRenderer { }), }); + debug!("Executing {} render jobs", self.render_jobs.len()); + // Pop off jobs from the queue as they're being processed while let Some(job) = self.render_jobs.pop_front() { - if let Some(pipeline) = self.render_pipelines.get(&job.material().shader_id) { + if let Some(pipeline) = self.render_pipelines.get(&job.mesh().material().shader_uuid.unwrap_or(0)) { // specify to use this pipeline render_pass.set_pipeline(pipeline.get_wgpu_pipeline()); @@ -674,11 +683,13 @@ impl Renderer for BasicRenderer { render_pass.set_index_buffer(indices.buffer().slice(..), wgpu::IndexFormat::Uint16); render_pass.draw_indexed(0..indices_len, 0, 0..1); } else { + let vertices = mesh.position().unwrap(); + render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); - render_pass.draw(0..mesh.vertices.len() as u32, 0..1); + render_pass.draw(0..vertices.len() as u32, 0..1); } } else { - warn!("Failure to find RenderPipeline with shader id of '{}'!", job.material().shader_id); + warn!("Failure to find RenderPipeline with shader id of '{}'!", job.mesh().material().shader_uuid.unwrap_or(0)); } } } @@ -706,7 +717,7 @@ impl Renderer for BasicRenderer { self.size } - fn add_render_pipeline(&mut self, shader_id: u32, pipeline: Arc) { + fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc) { self.render_pipelines.insert(shader_id, pipeline); } } \ No newline at end of file From a5b145c9b38e2d503f3ffddecb7666e781c8fdf5 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 29 Sep 2023 14:57:22 -0400 Subject: [PATCH 09/14] Create a testbed example to make developing the engine easier --- .gitignore | 2 +- examples/testbed/Cargo.lock | 3068 ++++++++++++++++++++ examples/testbed/Cargo.toml | 15 + examples/testbed/assets/cube-embedded.gltf | 121 + examples/testbed/assets/happy-tree.png | Bin 0 -> 28134 bytes examples/testbed/src/main.rs | 199 ++ 6 files changed, 3404 insertions(+), 1 deletion(-) create mode 100644 examples/testbed/Cargo.lock create mode 100644 examples/testbed/Cargo.toml create mode 100644 examples/testbed/assets/cube-embedded.gltf create mode 100755 examples/testbed/assets/happy-tree.png create mode 100644 examples/testbed/src/main.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..1de5659 100755 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/target +target \ No newline at end of file diff --git a/examples/testbed/Cargo.lock b/examples/testbed/Cargo.lock new file mode 100644 index 0000000..62ccafd --- /dev/null +++ b/examples/testbed/Cargo.lock @@ -0,0 +1,3068 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1061f3ff92c2f65800df1f12fc7b4ff44ee14783104187dd04dfee6f11b0fd2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-activity" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" +dependencies = [ + "android-properties", + "bitflags 1.3.2", + "cc", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum 0.6.1", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1da3ae8dabd9c00f453a329dfe1fb28da3c0a72e2478cdcd93171740c20499" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand 2.0.1", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.24", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-process" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf012553ce51eb7aa6dc2143804cc8252bd1cb681a1c5cb7fa94ca88682dee1d" +dependencies = [ + "async-io", + "async-lock", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.0.0", + "futures-lite", + "rustix 0.38.14", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-signal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99f3cb3f9ff89f7d718fbb942c9eb91bedff12e396adf09a622dfe7ffec2bc2" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "concurrent-queue", + "futures-core", + "futures-io", + "libc", + "signal-hook-registry", + "slab", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9441c6b2fe128a7c2bf680a44c34d0df31ce09e5b7e401fcca3faa483dbc921" + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomicell" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157342dd84c64f16899b4b16c1fb2cce54b887990362aac3c590b3d13810890f" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-sys" +version = "0.1.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +dependencies = [ + "block-sys", + "objc2-encode", +] + +[[package]] +name = "blocking" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c4ef1f913d78636d78d538eec1f18de81e481f44b1be0a81060090530846e1" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "fastrand 2.0.1", + "futures-io", + "futures-lite", + "piper", + "tracing", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "calloop" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +dependencies = [ + "bitflags 1.3.2", + "log", + "nix 0.25.1", + "slotmap", + "thiserror", + "vec_map", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "com-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" + +[[package]] +name = "concurrent-queue" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.9.0", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "d3d12" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" +dependencies = [ + "bitflags 1.3.2", + "libloading 0.7.4", + "winapi", +] + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.0", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "edict" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85bf7cde5687ce04b093bfe183453fa12996b6bdfd2567a0262ebd621761d77" +dependencies = [ + "atomicell", + "edict-proc", + "hashbrown 0.13.2", + "parking_lot", + "smallvec", + "tiny-fn", +] + +[[package]] +name = "edict-proc" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94d80dc0f05250a9082bb9455bbf3d6c6c51db388b060df914aebcfb4a9b9f1" +dependencies = [ + "edict-proc-lib", + "syn 2.0.37", +] + +[[package]] +name = "edict-proc-lib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d98f9931a4f71c7eb7d85cf4ef1271b27014625c85a65376a52c10ac4ffaea" +dependencies = [ + "proc-easy", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e56284f00d94c1bc7fd3c77027b4623c88c1f53d8d2394c6199f2921dea325" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fps_counter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aaba7ff514ee9d802b562927f80b1e94e93d8e74c31b134c9c3762dabf1a36b" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gilrs-core" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ccc99e9b8d63ffcaa334c4babfa31f46e156618a11f63efb6e8e6bcb37b830d" +dependencies = [ + "core-foundation", + "io-kit-sys", + "js-sys", + "libc", + "libudev-sys", + "log", + "nix 0.26.4", + "uuid", + "vec_map", + "wasm-bindgen", + "web-sys", + "windows 0.51.1", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "glow" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gltf" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2dcfb6dd7a66f9eb3d181a29dcfb22d146b0bcdc2e1ed1713cbf03939a88ea" +dependencies = [ + "base64 0.13.1", + "byteorder", + "gltf-json", + "image", + "lazy_static", + "urlencoding", +] + +[[package]] +name = "gltf-derive" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cbcea5dd47e7ad4e9ee6f040384fcd7204bbf671aa4f9e7ca7dfc9bfa1de20" +dependencies = [ + "inflections", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "gltf-json" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5b810806b78dde4b71a95cc0e6fdcab34c4c617da3574df166f9987be97d03" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "gpu-alloc" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" +dependencies = [ + "bitflags 1.3.2", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "gpu-allocator" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +dependencies = [ + "backtrace", + "log", + "thiserror", + "winapi", + "windows 0.44.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.4.0", + "gpu-descriptor-types", + "hashbrown 0.14.1", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.4.0", +] + +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hassle-rs" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90601c6189668c7345fc53842cb3f3a3d872203d523be1b3cb44a36a3e62fb85" +dependencies = [ + "bitflags 1.3.2", + "com-rs", + "libc", + "libloading 0.7.4", + "thiserror", + "widestring", + "winapi", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational 0.4.1", + "num-traits", + "png", + "qoi", + "tiff", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", +] + +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "io-kit-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2d4429acc1deff0fbdece0325b4997bdb02b2c245ab7023fd5deca0f6348de" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading 0.7.4", + "pkg-config", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +dependencies = [ + "value-bag", +] + +[[package]] +name = "lyra-engine" +version = "0.0.1" +dependencies = [ + "aligned-vec", + "anyhow", + "async-std", + "async-trait", + "atomicell", + "bytemuck", + "cfg-if", + "edict", + "gilrs-core", + "glam", + "image", + "instant", + "lyra-resource", + "petgraph", + "quote", + "stopwatch", + "syn 2.0.37", + "tobj", + "tracing", + "tracing-appender", + "tracing-log", + "tracing-subscriber", + "wgpu", + "winit", +] + +[[package]] +name = "lyra-resource" +version = "0.0.1" +dependencies = [ + "anyhow", + "base64 0.21.4", + "edict", + "glam", + "gltf", + "image", + "percent-encoding", + "thiserror", + "uuid", +] + +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "naga" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c3d4269bcb7d50121097702fde1afb75f4ea8083aeb7a55688dcf289a853271" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "codespan-reporting", + "hexf-parse", + "indexmap 1.9.3", + "log", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys", + "num_enum 0.5.11", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational 0.1.42", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" +dependencies = [ + "num-integer", + "num-traits", + "rand", + "rustc-serialize", +] + +[[package]] +name = "num-complex" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656" +dependencies = [ + "num-traits", + "rustc-serialize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "rustc-serialize", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive 0.5.11", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive 0.6.1", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[package]] +name = "objc2" +version = "0.3.0-beta.3.patch-leaks.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +dependencies = [ + "block2", + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "2.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "orbclient" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8378ac0dfbd4e7895f2d2c1f1345cab3836910baf3a300b000d04250f0c8428f" +dependencies = [ + "redox_syscall", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owned_ttf_parser" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.0.2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "proc-easy" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea59c637cd0e6b71ae18e589854e9de9b7cb17fefdbf2047e42bd38e24285b19" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89dff0959d98c9758c88826cc002e2c3d0b9dfac4139711d1f30de442f1139b" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "range-alloc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[package]] +name = "rustix" +version = "0.37.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.7", + "windows-sys 0.48.0", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b21f559e07218024e7e9f90f96f601825397de0e25420135f7f952453fed0b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.24.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags 1.3.2", + "num-traits", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stopwatch" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d04b5ebc78da44d3a456319d8bc2783e7d8cc7ccbb5cb4dc3f54afbd93bf728" +dependencies = [ + "num", +] + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "testbed" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-std", + "fps_counter", + "lyra-engine", + "tracing", +] + +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tiny-fn" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7b2c33e09916c65a15c92c1e583946052527e06102689ed11c6125f64fa8ba" + +[[package]] +name = "tiny-skia" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tobj" +version = "3.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57381207291289bad19de63acd3fbf5948ff99b2868116c367b7224c37d55f90" +dependencies = [ + "ahash", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.0.2", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +dependencies = [ + "crossbeam-channel", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "ttf-parser" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "value-bag" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags 1.3.2", + "downcast-rs", + "libc", + "nix 0.24.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.3", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags 1.3.2", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "wgpu" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d745a1b6d91d85c33defbb29f0eee0450e1d2614d987e14bf6baf26009d132d7" +dependencies = [ + "arrayvec", + "cfg-if", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131408d940e335792645a98f03639573b0480e9e2e7cddbbab74f7c6d9f3fff" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 1.3.2", + "codespan-reporting", + "fxhash", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdcf61a283adc744bb5453dd88ea91f3f86d5ca6b027661c6c73c7734ae0288b" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 1.3.2", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.7.4", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32444e121b0bd00cb02c0de32fde457a9491bd44e03e7a5db6df9b1da2f6f110" +dependencies = [ + "bitflags 1.3.2", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winit" +version = "0.28.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" +dependencies = [ + "android-activity", + "bitflags 1.3.2", + "cfg_aliases", + "core-foundation", + "core-graphics", + "dispatch", + "instant", + "libc", + "log", + "mio", + "ndk", + "objc2", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle", + "redox_syscall", + "sctk-adwaita", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-commons", + "wayland-protocols", + "wayland-scanner", + "web-sys", + "windows-sys 0.45.0", + "x11-dl", +] + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/examples/testbed/Cargo.toml b/examples/testbed/Cargo.toml new file mode 100644 index 0000000..25efcb5 --- /dev/null +++ b/examples/testbed/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "testbed" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lyra-engine = { path = "../../", version = "0.0.1" } +anyhow = "1.0.75" +async-std = "1.12.0" +tracing = "0.1.37" +fps_counter = "2.0.0" + +[workspace] \ No newline at end of file diff --git a/examples/testbed/assets/cube-embedded.gltf b/examples/testbed/assets/cube-embedded.gltf new file mode 100644 index 0000000..5d42356 --- /dev/null +++ b/examples/testbed/assets/cube-embedded.gltf @@ -0,0 +1,121 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v3.6.5", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"Cube", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "TEXCOORD_0":1, + "NORMAL":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "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 + } + ], + "buffers":[ + { + "byteLength":840, + "uri":"data:application/octet-stream;base64,AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AAAAPgAAAD8AAMA+AABAPwAAwD4AAEA/AAAgPwAAAAAAACA/AACAPwAAYD8AAIA+AAAAPgAAgD4AAMA+AAAAAAAAwD4AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAQAOABQAAQAUAAcACgAGABIACgASABYAFwATAAwAFwAMABAADwADAAkADwAJABUABQACAAgABQAIAAsAEQANAAAAEQAAAAQA" + } + ] +} diff --git a/examples/testbed/assets/happy-tree.png b/examples/testbed/assets/happy-tree.png new file mode 100755 index 0000000000000000000000000000000000000000..fc86db345c14b95728250b8914fa284f34193834 GIT binary patch literal 28134 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 D;T5;R literal 0 HcmV?d00001 diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs new file mode 100644 index 0000000..9a639ac --- /dev/null +++ b/examples/testbed/src/main.rs @@ -0,0 +1,199 @@ +use std::ops::DerefMut; + +use lyra_engine::{math, ecs::{World, components::{mesh::MeshComponent, transform::TransformComponent, camera::CameraComponent, model::ModelComponent}, atomicell::{Ref, RefMut}, EventQueue}, render::{mesh::Mesh, material::Material, vertex::Vertex, window::{CursorGrabMode, WindowOptions}}, math::Transform, input::{KeyCode, InputButtons, MouseMotion}, game::Game, change_tracker::Ct}; + +use lyra_engine::assets::{ResourceManager, Texture, Model}; + +use tracing::debug; + +pub const VERTICES: &[Vertex] = &[ + Vertex { position: [-0.0868241, 0.49240386, 0.0], tex_coords: [0.4131759, 0.00759614], }, // A + Vertex { position: [-0.49513406, 0.06958647, 0.0], tex_coords: [0.0048659444, 0.43041354], }, // B + Vertex { position: [-0.21918549, -0.44939706, 0.0], tex_coords: [0.28081453, 0.949397], }, // C + Vertex { position: [0.35966998, -0.3473291, 0.0], tex_coords: [0.85967, 0.84732914], }, // D + Vertex { position: [0.44147372, 0.2347359, 0.0], tex_coords: [0.9414737, 0.2652641], }, // E +]; + +pub const INDICES: &[u16] = &[ + 0, 1, 4, + 1, 2, 4, + 2, 3, 4, +]; + +#[derive(Debug, Default)] +struct Point2d { + x: i32, + y: i32, +} + +impl std::fmt::Display for Point2d { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(x={}, y={})", self.x, self.y) + } +} + +impl Point2d { + pub fn new(x: i32, y: i32) -> Self { + Self { + x, + y, + } + } +} + +#[derive(Debug, Default)] +struct Point3d { + x: i32, + y: i32, + z: i32, +} + +impl std::fmt::Display for Point3d { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(x={}, y={}, z={})", self.x, self.y, self.z) + } +} + +impl Point3d { + pub fn new(x: i32, y: i32, z: i32) -> Self { + Self { + x, + y, + z, + } + } +} + +#[async_std::main] +async fn main() { + let setup_sys = |world: &mut World| -> anyhow::Result<()> { + { + let mut window_options = world.get_resource_mut::>().unwrap(); + window_options.cursor_grab = CursorGrabMode::Confined; + window_options.cursor_visible = false; + } + + let mut resman = world.get_resource_mut::().unwrap(); + let diffuse_texture = resman.request::("assets/happy-tree.png").unwrap(); + let cube_model = resman.request::("assets/cube-embedded.gltf").unwrap(); + drop(resman); + + /* world.spawn((MeshComponent::new( + Mesh { + vertices: VERTICES.to_vec(), + indices: Some(INDICES.to_vec()) + }, Material { + shader_id: 0, + texture: diffuse_texture.clone() + }), + TransformComponent::from(Transform::from_xyz(0.005, 0.0, -2.0)), + )); + + world.spawn((MeshComponent::new( + Mesh { + vertices: VERTICES.to_vec(), + indices: Some(INDICES.to_vec()) + }, Material { + shader_id: 0, + texture: diffuse_texture + }), + TransformComponent::from(Transform::from_xyz(0.005, 0.7, -0.5)), + )); */ + + world.spawn(( + ModelComponent(cube_model), + TransformComponent::from(Transform::from_xyz(0.005, 0.5, -1.2)), + )); + + let mut camera = CameraComponent::new_3d(); + camera.transform.translation += math::Vec3::new(0.0, 0.0, 2.0); + //camera.transform.rotate_y(Angle::Degrees(-25.0)); + camera.transform.rotate_z(math::Angle::Degrees(-90.0)); + world.spawn((camera,)); + + Ok(()) + }; + + //world.insert_resource(fps_counter::FPSCounter::new()); + let fps_system = |world: &mut World| -> anyhow::Result<()> { + let mut counter: RefMut = world.get_resource_mut().unwrap(); + + let fps = counter.tick(); + + debug!("FPS: {fps}"); + + Ok(()) + }; + let fps_plugin = move |game: &mut Game| { + let world = game.world(); + world.insert_resource(fps_counter::FPSCounter::new()); + + game.with_system("fps", fps_system, &["input"]); + }; + + let jiggle_system = |world: &mut World| -> anyhow::Result<()> { + let keys = world.get_resource(); + if keys.is_none() { + return Ok(()); + } + + let keys: Ref> = keys.unwrap(); + + let speed = 0.001; + + let mut dir_x = 0.0; + let mut dir_y = 0.0; + + if keys.is_pressed(KeyCode::A) { + dir_x += speed; + } + + if keys.is_pressed(KeyCode::D) { + dir_x -= speed; + } + + if keys.is_pressed(KeyCode::S) { + dir_y += speed; + } + + if keys.is_pressed(KeyCode::W) { + dir_y -= speed; + } + + drop(keys); + + if dir_x == 0.0 && dir_y == 0.0 { + return Ok(()); + } + + //debug!("moving by ({}, {})", dir_x, dir_y); + + for transform in world.query_mut::<(&mut TransformComponent,)>().iter_mut() { + let t = &mut transform.transform; + debug!("Translation: {}", t.translation); + + /* t.translation += glam::Vec3::new(0.0, 0.001, 0.0); + t.translation.x *= -1.0; */ + t.translation.x += dir_x; + t.translation.y += dir_y; + } + + let events = world.get_resource_mut::().unwrap(); + if let Some(mm) = events.read_events::() { + debug!("Mouse motion: {:?}", mm); + } + + Ok(()) + }; + + let jiggle_plugin = move |game: &mut Game| { + game.with_system("jiggle", jiggle_system, &["input"]); + }; + + Game::initialize().await + .with_plugin(lyra_engine::DefaultPlugins) + .with_startup_system(setup_sys) + //.with_plugin(fps_plugin) + .with_plugin(jiggle_plugin) + .run().await; +} From 8f7288339d5db789a8e5d12d35b73db3d6b12327 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 5 Oct 2023 11:42:24 -0400 Subject: [PATCH 10/14] Add default texture which fixes render error, fix cube rendering --- .vscode/launch.json | 10 ++-- Cargo.lock | 1 + examples/testbed/Cargo.lock | 1 + examples/testbed/src/main.rs | 39 +++++++++++--- lyra-resource/Cargo.toml | 1 + lyra-resource/src/loader/model.rs | 3 ++ src/render/default_texture.png | Bin 0 -> 545 bytes src/render/renderer.rs | 86 +++++++++++++++--------------- src/render/shaders/base.wgsl | 4 +- src/render/vertex.rs | 10 ++-- 10 files changed, 93 insertions(+), 62 deletions(-) create mode 100644 src/render/default_texture.png diff --git a/.vscode/launch.json b/.vscode/launch.json index 521ee82..cdff0ce 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,20 +7,20 @@ { "type": "lldb", "request": "launch", - "name": "Debug executable 'lyra-engine'", + "name": "Debug lyra testbed", "cargo": { "args": [ "build", - "--bin=lyra-engine", - "--package=lyra-engine" + "--manifest-path", "${workspaceFolder}/examples/testbed/Cargo.toml" + //"--bin=testbed", ], "filter": { - "name": "lyra-engine", + "name": "testbed", "kind": "bin" } }, "args": [], - "cwd": "${workspaceFolder}" + "cwd": "${workspaceFolder}/examples/testbed" }, { "type": "lldb", diff --git a/Cargo.lock b/Cargo.lock index 179240b..1ff9632 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -1304,6 +1304,7 @@ dependencies = [ "image", "percent-encoding", "thiserror", + "tracing", "uuid", ] diff --git a/examples/testbed/Cargo.lock b/examples/testbed/Cargo.lock index 62ccafd..16d56f0 100644 --- a/examples/testbed/Cargo.lock +++ b/examples/testbed/Cargo.lock @@ -1343,6 +1343,7 @@ dependencies = [ "image", "percent-encoding", "thiserror", + "tracing", "uuid", ] diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 9a639ac..f845d71 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -6,13 +6,13 @@ use lyra_engine::assets::{ResourceManager, Texture, Model}; use tracing::debug; -pub const VERTICES: &[Vertex] = &[ +/* pub const VERTICES: &[Vertex] = &[ Vertex { position: [-0.0868241, 0.49240386, 0.0], tex_coords: [0.4131759, 0.00759614], }, // A Vertex { position: [-0.49513406, 0.06958647, 0.0], tex_coords: [0.0048659444, 0.43041354], }, // B Vertex { position: [-0.21918549, -0.44939706, 0.0], tex_coords: [0.28081453, 0.949397], }, // C Vertex { position: [0.35966998, -0.3473291, 0.0], tex_coords: [0.85967, 0.84732914], }, // D Vertex { position: [0.44147372, 0.2347359, 0.0], tex_coords: [0.9414737, 0.2652641], }, // E -]; +]; */ pub const INDICES: &[u16] = &[ 0, 1, 4, @@ -67,11 +67,11 @@ impl Point3d { #[async_std::main] async fn main() { let setup_sys = |world: &mut World| -> anyhow::Result<()> { - { + /* { let mut window_options = world.get_resource_mut::>().unwrap(); window_options.cursor_grab = CursorGrabMode::Confined; window_options.cursor_visible = false; - } + } */ let mut resman = world.get_resource_mut::().unwrap(); let diffuse_texture = resman.request::("assets/happy-tree.png").unwrap(); @@ -102,11 +102,11 @@ async fn main() { world.spawn(( ModelComponent(cube_model), - TransformComponent::from(Transform::from_xyz(0.005, 0.5, -1.2)), + TransformComponent::from(Transform::from_xyz(0.005, 0.5, -2.2)), )); let mut camera = CameraComponent::new_3d(); - camera.transform.translation += math::Vec3::new(0.0, 0.0, 2.0); + camera.transform.translation += math::Vec3::new(0.0, 0.0, 7.5); //camera.transform.rotate_y(Angle::Degrees(-25.0)); camera.transform.rotate_z(math::Angle::Degrees(-90.0)); world.spawn((camera,)); @@ -140,10 +140,14 @@ async fn main() { let keys: Ref> = keys.unwrap(); let speed = 0.001; + let rot_speed = 1.0; let mut dir_x = 0.0; let mut dir_y = 0.0; + let mut rot_x = 0.0; + let mut rot_y = 0.0; + if keys.is_pressed(KeyCode::A) { dir_x += speed; } @@ -160,9 +164,25 @@ async fn main() { dir_y -= speed; } + if keys.is_pressed(KeyCode::Left) { + rot_y -= rot_speed; + } + + if keys.is_pressed(KeyCode::Right) { + rot_y += rot_speed; + } + + if keys.is_pressed(KeyCode::Up) { + rot_x -= rot_speed; + } + + if keys.is_pressed(KeyCode::Down) { + rot_x += rot_speed; + } + drop(keys); - if dir_x == 0.0 && dir_y == 0.0 { + if dir_x == 0.0 && dir_y == 0.0 && rot_x == 0.0 && rot_y == 0.0 { return Ok(()); } @@ -170,12 +190,15 @@ async fn main() { for transform in world.query_mut::<(&mut TransformComponent,)>().iter_mut() { let t = &mut transform.transform; - debug!("Translation: {}", t.translation); + //debug!("Translation: {}", t.translation); + //debug!("Rotation: {}", t.rotation); /* t.translation += glam::Vec3::new(0.0, 0.001, 0.0); t.translation.x *= -1.0; */ t.translation.x += dir_x; t.translation.y += dir_y; + t.rotate_x(math::Angle::Degrees(rot_x)); + t.rotate_y(math::Angle::Degrees(rot_y)); } let events = world.get_resource_mut::().unwrap(); diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index f5a7799..1dda54f 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -14,4 +14,5 @@ gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness"] } image = "0.24.7" percent-encoding = "2.3.0" thiserror = "1.0.48" +tracing = "0.1.37" uuid = { version = "1.4.1", features = ["v4"] } diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index 3b878f2..731e115 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -4,6 +4,8 @@ use base64::Engine; use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, ResHandle}; +use tracing::debug; + impl From for LoaderError { fn from(value: gltf::Error) -> Self { LoaderError::DecodingError(value.into()) @@ -40,6 +42,7 @@ impl ModelLoader { // read the positions if let Some(pos) = reader.read_positions() { + debug!("Mesh mode is {:?}", prim.mode()); let pos: Vec = pos.map(|t| t.into()).collect(); new_mesh.add_attribute(MeshVertexAttribute::Position, VertexAttributeData::Vec3(pos)); } diff --git a/src/render/default_texture.png b/src/render/default_texture.png new file mode 100644 index 0000000000000000000000000000000000000000..ec02f5b9165bbf8467a1c3f726ba89ec80ee7631 GIT binary patch literal 545 zcmV++0^a?JP)EX>4Tx04R}tkv&MmKpe$i(~43mf_4yb$WWc^q9Ts93Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4RLx;QDiNQwVT3N2zhIPS;0dyl(!fY7Wm%?c#|O}EWV zDkNtSqa<=fo2xU6A;Z>x##3oJ%eXJTq!$GxNkzVzJc4N*A-TsS!^T$5c(Hd?Dwt z%6W^kR;{!4J^2eG1$||i>oiA^!XlO+L4<-jDyYInoK~F_3mMu^`uIm&zeFyDToo{K zET9Grvg-%`gWug+#i>azDUt+wUL5CR4CvhjS`EkfK6aee2@re+uJn$-)&yohNpEzt z*by+W4P0DzG-VIC+yRE44B3=jDM(W&7J>IO`ldWEbPI&nyx!XTIDG)J)K&ThI5-5x zOO(Ct^X|Ux-u^w)?(YZpv~qO|>Y5$^000SaNLh0L04^f{04^f|c%?sf00007bV*G` z2j>a{0uu@yE=o55000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0000C jNkl, texture_bindgroup: Option, - texture_layout: Option, /// The index of the transform for this entity. /// The tuple is structured like this: (transform index, index of transform inside the buffer) @@ -155,6 +154,8 @@ pub struct BasicRenderer { camera_buffer: wgpu::Buffer, camera_bind_group: wgpu::BindGroup, + texture_bind_group_layout: BindGroupLayout, + default_texture_bind_group: BindGroup, depth_buffer_texture: RenderTexture, } @@ -343,12 +344,27 @@ impl BasicRenderer { let depth_texture = RenderTexture::create_depth_texture(&device, &config, "Depth Buffer"); - let mut pipelines = HashMap::new(); - pipelines.insert(0, Arc::new(FullRenderPipeline::new(&device, &config, &shader, - vec![super::vertex::Vertex::desc(),], - vec![&texture_bind_group_layout, &transform_bind_group_layout, &camera_bind_group_layout]))); + // load the default texture + let bytes = include_bytes!("default_texture.png"); + let tex = RenderTexture::from_bytes(&device, &queue, bytes, "default_texture").unwrap(); + let default_tex_bindgroup = device.create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(tex.view()), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(tex.sampler()), + } + ], + label: Some("default_texture"), + } + ); - Self { + let mut s = Self { window, surface, device, @@ -361,7 +377,7 @@ impl BasicRenderer { b: 0.3, a: 1.0, }, - render_pipelines: pipelines, + render_pipelines: HashMap::new(), render_jobs: VecDeque::new(), buffer_storage: HashMap::new(), @@ -373,8 +389,19 @@ impl BasicRenderer { camera_buffer, camera_bind_group, + texture_bind_group_layout, + default_texture_bind_group: default_tex_bindgroup, depth_buffer_texture: depth_texture, - } + }; + + // create the default pipelines + let mut pipelines = HashMap::new(); + pipelines.insert(0, Arc::new(FullRenderPipeline::new(&s.device, &s.config, &shader, + vec![super::vertex::Vertex::desc(),], + vec![&s.texture_bind_group_layout, &s.transform_bind_group_layout, &camera_bind_group_layout]))); + s.render_pipelines = pipelines; + + s } fn find_next_multiple(n: u32, mul: u32) -> u32 { @@ -385,13 +412,13 @@ impl BasicRenderer { } } - // TODO: minimize how often model buffers are updated by checking if they changed fn update_mesh_buffers(&mut self, entity: EntityId, mesh: &Mesh) { if let Some(buffers) = self.buffer_storage.get_mut(&entity) { // check if the buffer sizes dont match. If they dont, completely remake the buffers let vertices = mesh.position().unwrap(); if buffers.buffer_vertex.count() != vertices.len() { drop(buffers); + debug!("Recreating buffers for mesh"); let (vert, idx) = self.create_vertex_index_buffers(mesh); // have to re-get buffers because of borrow checker @@ -456,38 +483,13 @@ 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_layout, diffuse_bindgroup) = if let Some(model_texture) = &mesh.material().texture { + let diffuse_bindgroup = if let Some(model_texture) = &mesh.material().texture { let image = &model_texture.data.as_ref().unwrap().image; let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap(); - let texture_bind_group_layout = - self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - // This should match the filterable field of the - // corresponding Texture entry above. - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); - let diffuse_bind_group = self.device.create_bind_group( &wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, + layout: &self.texture_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, @@ -502,16 +504,15 @@ impl BasicRenderer { } ); - (Some(texture_bind_group_layout), Some(diffuse_bind_group)) + Some(diffuse_bind_group) } else { - (None, None) + None }; RenderBufferStorage { buffer_vertex: vertex_buffer, buffer_indices, render_texture: None, - texture_layout: diffuse_layout, texture_bindgroup: diffuse_bindgroup, transform_index: transform_indices } @@ -561,7 +562,6 @@ impl Renderer for BasicRenderer { let mut alive_entities = HashSet::new(); for (entity, model, model_epoch, transform) in main_world.query::<(Entities, &ModelComponent, EpochOf, &TransformComponent)>().iter() { - debug!("Collecting model things"); let model = model.data.as_ref().unwrap().as_ref(); let model_mesh = model.meshes.first().unwrap(); @@ -649,8 +649,6 @@ impl Renderer for BasicRenderer { }), }); - debug!("Executing {} render jobs", self.render_jobs.len()); - // Pop off jobs from the queue as they're being processed while let Some(job) = self.render_jobs.pop_front() { if let Some(pipeline) = self.render_pipelines.get(&job.mesh().material().shader_uuid.unwrap_or(0)) { @@ -664,6 +662,8 @@ impl Renderer for BasicRenderer { // Bind the optional texture if let Some(tex) = buffers.texture_bindgroup.as_ref() { render_pass.set_bind_group(0, &tex, &[]); + } else { + render_pass.set_bind_group(0, &self.default_texture_bind_group, &[]); } // Get the bindgroup for job's transform and bind to it using an offset. @@ -680,7 +680,7 @@ impl Renderer for BasicRenderer { let indices_len = indices.count() as u32; render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); - render_pass.set_index_buffer(indices.buffer().slice(..), wgpu::IndexFormat::Uint16); + render_pass.set_index_buffer(indices.buffer().slice(..), wgpu::IndexFormat::Uint32); render_pass.draw_indexed(0..indices_len, 0, 0..1); } else { let vertices = mesh.position().unwrap(); diff --git a/src/render/shaders/base.wgsl b/src/render/shaders/base.wgsl index c8e6ce1..6361232 100755 --- a/src/render/shaders/base.wgsl +++ b/src/render/shaders/base.wgsl @@ -2,7 +2,7 @@ struct VertexInput { @location(0) position: vec3, - @location(1) tex_coords: vec2, + //@location(1) tex_coords: vec2, } struct VertexOutput { @@ -25,7 +25,7 @@ fn vs_main( model: VertexInput, ) -> VertexOutput { var out: VertexOutput; - out.tex_coords = model.tex_coords; + out.tex_coords = vec2(1.0, 1.0); out.clip_position = camera.view_proj * u_model_transform * vec4(model.position, 1.0); return out; } diff --git a/src/render/vertex.rs b/src/render/vertex.rs index 12ac319..0f18f77 100755 --- a/src/render/vertex.rs +++ b/src/render/vertex.rs @@ -1,3 +1,5 @@ +use glam::Vec3; + use super::desc_buf_lay::DescVertexBufferLayout; #[repr(C)] @@ -5,13 +7,13 @@ use super::desc_buf_lay::DescVertexBufferLayout; pub struct Vertex { pub position: [f32; 3], //pub color: [f32; 3], // TODO: add color again - pub tex_coords: [f32; 2] + //pub tex_coords: [f32; 2] } impl DescVertexBufferLayout for Vertex { fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, + array_stride: std::mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { @@ -19,11 +21,11 @@ impl DescVertexBufferLayout for Vertex { shader_location: 0, format: wgpu::VertexFormat::Float32x3, }, - wgpu::VertexAttribute { + /* wgpu::VertexAttribute { offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, shader_location: 1, format: wgpu::VertexFormat::Float32x2, - } + } */ ] } } From 02a0eea7b3a2c82f04f21d21fcc90be4e4ac799b Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 8 Oct 2023 00:03:53 -0400 Subject: [PATCH 11/14] Don't force loaded model indicies to U32 --- lyra-resource/src/loader/model.rs | 11 ++++---- lyra-resource/src/model.rs | 30 +++++++++++++++++++-- src/render/render_pipeline.rs | 4 +-- src/render/renderer.rs | 44 +++++++++++++++---------------- 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index 731e115..a1cb9e0 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use base64::Engine; -use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, ResHandle}; +use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, MeshIndices}; use tracing::debug; @@ -67,10 +67,11 @@ impl ModelLoader { // read the indices if let Some(indices) = reader.read_indices() { - let indices: Vec = match indices { - gltf::mesh::util::ReadIndices::U8(i) => i.map(|i| i as u32).collect(), - gltf::mesh::util::ReadIndices::U16(i) => i.map(|i| i as u32).collect(), - gltf::mesh::util::ReadIndices::U32(i) => i.collect(), + let indices: MeshIndices = match indices { + // wpgu doesn't support u8 indices, so those must be converted to u16 + gltf::mesh::util::ReadIndices::U8(i) => MeshIndices::U16(i.map(|i| i as u16).collect()), + gltf::mesh::util::ReadIndices::U16(i) => MeshIndices::U16(i.collect()), + gltf::mesh::util::ReadIndices::U32(i) => MeshIndices::U32(i.collect()), }; new_mesh.indices = Some(indices); diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index e83fe3d..652cef2 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -1,6 +1,32 @@ use std::collections::HashMap; -use crate::{Material, ResHandle}; +use crate::Material; + +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +pub enum MeshIndices { + //U8(Vec), + U16(Vec), + U32(Vec), +} + +/* impl From> for MeshIndices { + fn from(value: Vec) -> Self { + MeshIndices::U8(value) + } +} */ + +impl From> for MeshIndices { + fn from(value: Vec) -> Self { + MeshIndices::U16(value) + } +} + +impl From> for MeshIndices { + fn from(value: Vec) -> Self { + MeshIndices::U32(value) + } +} #[repr(C)] #[derive(Clone, Debug, PartialEq)] @@ -45,7 +71,7 @@ pub enum MeshVertexAttribute { #[derive(Clone, Default, edict::Component)] pub struct Mesh { pub attributes: HashMap, - pub indices: Option>, + pub indices: Option, material: Option, } diff --git a/src/render/render_pipeline.rs b/src/render/render_pipeline.rs index d1a7f20..87f876f 100755 --- a/src/render/render_pipeline.rs +++ b/src/render/render_pipeline.rs @@ -1,8 +1,8 @@ -use std::{ops::Range, cell::Ref}; +use std::ops::Range; use wgpu::{PipelineLayout, RenderPipeline, RenderPass, VertexBufferLayout, BindGroupLayout}; -use super::{render_job::RenderJob, vertex::Vertex, desc_buf_lay::DescVertexBufferLayout, texture::RenderTexture}; +use super::{render_job::RenderJob, texture::RenderTexture}; pub struct FullRenderPipeline { layout: PipelineLayout, diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 0478f5d..f51bde1 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -36,7 +36,7 @@ pub trait Renderer { struct RenderBufferStorage { buffer_vertex: BufferStorage, - buffer_indices: Option, + buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>, render_texture: Option, texture_bindgroup: Option, @@ -404,14 +404,6 @@ impl BasicRenderer { s } - fn find_next_multiple(n: u32, mul: u32) -> u32 { - if n % mul == 0 { - n - } else { - n + (mul - n % mul) - } - } - fn update_mesh_buffers(&mut self, entity: EntityId, mesh: &Mesh) { if let Some(buffers) = self.buffer_storage.get_mut(&entity) { // check if the buffer sizes dont match. If they dont, completely remake the buffers @@ -438,16 +430,19 @@ impl BasicRenderer { // update the indices if they're given if let Some(index_buffer) = buffers.buffer_indices.as_ref() { - let index_buffer = index_buffer.buffer(); - let indices = mesh.indices.as_ref().unwrap().as_slice(); - let (_, indices, _) = bytemuck::pod_align_to::(indices); // TODO: Don't force indicies into u32 - - self.queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(&indices)); + let aligned_indices = match mesh.indices.as_ref().unwrap() { + // U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size. + lyra_resource::MeshIndices::U16(v) => bytemuck::pod_align_to::(v).1, + lyra_resource::MeshIndices::U32(v) => bytemuck::pod_align_to::(v).1, + }; + + let index_buffer = index_buffer.1.buffer(); + self.queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(&aligned_indices)); } } } - fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option) { + fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) { let vertices = mesh.position().unwrap(); let vertex_buffer = self.device.create_buffer_init( &wgpu::util::BufferInitDescriptor { @@ -458,26 +453,31 @@ impl BasicRenderer { ); let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertices.len()); - let buffer_indices = match mesh.indices.as_ref() { + let indices = match mesh.indices.as_ref() { Some(indices) => { + let (idx_type, len, contents) = match indices { + lyra_resource::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(&v)), + lyra_resource::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(&v)), + }; + let index_buffer = self.device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), - contents: bytemuck::cast_slice(&indices), + contents, usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages:: COPY_DST, } ); - let buffer_indices = BufferStorage::new(index_buffer, 0, indices.len()); + let buffer_indices = BufferStorage::new(index_buffer, 0, len); - Some(buffer_indices) + Some((idx_type, buffer_indices)) }, None => { None } }; - ( vertex_buffer, buffer_indices ) + ( vertex_buffer, indices ) } fn create_mesh_buffers(&mut self, mesh: &Mesh, transform_indices: TransformBufferIndices) -> RenderBufferStorage { @@ -676,11 +676,11 @@ impl Renderer for BasicRenderer { render_pass.set_bind_group(2, &self.camera_bind_group, &[]); // if this mesh uses indices, use them to draw the mesh - if let Some(indices) = buffers.buffer_indices.as_ref() { + if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() { let indices_len = indices.count() as u32; render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); - render_pass.set_index_buffer(indices.buffer().slice(..), wgpu::IndexFormat::Uint32); + render_pass.set_index_buffer(indices.buffer().slice(..), idx_type.clone()); render_pass.draw_indexed(0..indices_len, 0, 0..1); } else { let vertices = mesh.position().unwrap(); From fd9f4bee2a9619ae6ad87391b62c7fc996029389 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Tue, 17 Oct 2023 22:04:25 -0400 Subject: [PATCH 12/14] Implement loading material textures from gltf and rendering them --- Cargo.lock | 14 ++ examples/testbed/Cargo.lock | 14 ++ .../testbed/assets/cube-texture-embedded.gltf | 142 ++++++++++++ examples/testbed/src/main.rs | 22 +- lyra-resource/Cargo.toml | 3 + lyra-resource/src/lib.rs | 4 +- .../src/loader/{texture.rs => image.rs} | 48 ++++- lyra-resource/src/loader/mod.rs | 22 +- lyra-resource/src/loader/model.rs | 59 +++-- lyra-resource/src/material.rs | 202 ++++++++++++++++-- lyra-resource/src/model.rs | 9 + lyra-resource/src/resource_manager.rs | 61 +++++- lyra-resource/src/util.rs | 38 ++++ lyra-resource/test_files/gltf/texture-bin.glb | Bin 0 -> 42320 bytes .../test_files/gltf/texture-embedded.gltf | 142 ++++++++++++ .../test_files/gltf/texture-sep/Green.png | Bin 0 -> 5735 bytes .../gltf/texture-sep/texture-sep.bin | Bin 0 -> 840 bytes .../gltf/texture-sep/texture-sep.gltf | 137 ++++++++++++ .../test_files/gltf/texture-sep/uvgrid.png | Bin 0 -> 40222 bytes src/render/renderer.rs | 20 +- src/render/shaders/base.wgsl | 4 +- src/render/vertex.rs | 24 ++- 22 files changed, 891 insertions(+), 74 deletions(-) create mode 100644 examples/testbed/assets/cube-texture-embedded.gltf rename lyra-resource/src/loader/{texture.rs => image.rs} (53%) create mode 100644 lyra-resource/src/util.rs create mode 100644 lyra-resource/test_files/gltf/texture-bin.glb create mode 100644 lyra-resource/test_files/gltf/texture-embedded.gltf create mode 100644 lyra-resource/test_files/gltf/texture-sep/Green.png create mode 100644 lyra-resource/test_files/gltf/texture-sep/texture-sep.bin create mode 100644 lyra-resource/test_files/gltf/texture-sep/texture-sep.gltf create mode 100644 lyra-resource/test_files/gltf/texture-sep/uvgrid.png diff --git a/Cargo.lock b/Cargo.lock index 1ff9632..7a09e3c 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/examples/testbed/Cargo.lock b/examples/testbed/Cargo.lock index 16d56f0..3893931 100644 --- a/examples/testbed/Cargo.lock +++ b/examples/testbed/Cargo.lock @@ -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" diff --git a/examples/testbed/assets/cube-texture-embedded.gltf b/examples/testbed/assets/cube-texture-embedded.gltf new file mode 100644 index 0000000..5309758 --- /dev/null +++ b/examples/testbed/assets/cube-texture-embedded.gltf @@ -0,0 +1,142 @@ +{ + "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":[ + { + "bufferView":4, + "mimeType":"image/png", + "name":"uvgrid" + } + ], + "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 + }, + { + "buffer":0, + "byteLength":40222, + "byteOffset":840 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":41064, + "uri":"data:application/octet-stream;base64,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xn/zf/P/8f/oF5M6zvfN/9fN//0EID5ZXhMgQIAAAQIECBAgQIAAgW8qYAHgmzas2yJAgAABAgQIECBAgAABAlFg9QJA3A4cK856nb39v7+v7O0/ffzs7X99/Oztv338uB2o/yyjHLcDZsTrY8TtwP1nGWX5P/43f7faUf5Pt3iGfyb/x//Nn1uNaPwf/zd/bvkb/3N//tO3hfG/+PlP+JtffZvHsvF/ihzpr7/6+L96ASBdXEACBAgQIECAAAECBAgQIEBgtoAFgNlkTiBAgAABAgQIECBAgAABAl9PwALA12szV0yAAAECBAgQIECAAAECBGYLWACYTeYEAgQIECBAgAABAgQIECDw9QQsAHy9NnPFBAgQIECAAAECBAgQIEBgtoAFgNlkTiBAgAABAgQIECBAgAABAl9PwALA12szV0yAAAECBAgQIECAAAECBGYLWACYTeYEAgQIECBAgAABAgQIECDw9QQsAHy9NnPFBAgQIECAAAECBAgQIEBgtoAFgNlkTiBAgAABAgQIECBAgAABAl9PwALA12szV0yAAAECBAgQIECAAAECBGYLWACYTeYEAgQIECBAgAABAgQIECDw9QR+PT4+ll71y8tLaXz3r/0rH0DPv/yvfP70f/q/yudP/6f/q3z+9H/6v8rnT/+n/6t8/uwAqNQXmwABAgQIECBAgAABAgQIJAlYAEiCFoYAAQIECBAgQIAAAQIECFQKWACo1BebAAECBAgQIECAAAECBAgkCVgASIIWhgABAgQIECBAgAABAgQIVApYAKjUF5sAAQIECBAgQIAAAQIECCQJWABIghaGAAECBAgQIECAAAECBAhUClgAqNQXmwABAgQIECBAgAABAgQIJAlYAEiCFoYAAQIECBAgQIAAAQIECFQKWACo1BebAAECBAgQIECAAAECBAgkCVgASIIWhgABAgQIECBAgAABAgQIVApYAKjUF5sAAQIECBAgQIAAAQIECCQJrF4AeJ32SZd6Pcz57nj9g6R3p33x/R/vku70epjX/XT9g6R3j0/npEjXw+yn6foHSe8ez09Jka6Hkf/y//qTkfOu/Jf/OU/a9SjG/+k6TNK78l/+Jz1qV8PI/+mqS9abPz7/V37/Wb0AkNXQ4hAgQIAAAQIECBAgQIAAAQLLBSwALLdzJgECBAgQIECAAAECBAgQ+DICv5Zc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xn/j//gv/TH/4nhv/Df/N/+fYnoMfx37u4rvP7G9K77/xe+7a7//+gnA8MdVAAIECBAgQIAAAQIECBAgUC9gAaC+DVwBAQIECBAgQIAAAQIECBAYLrB6ASBuBx5+tVcCxO0QVz4e/lbcDjI82JUAcTvMlY+Hv5W9/be/oezt/338uB2w/yyjHLcDZ8TrY8j/qSdJLcv/3O2/fePK/9yfP/T+xv/xf/OnN49l43/x85/wN39ie/evjf9TT5Ja/vHjf8LfPLvVoNXf/9aOf6sXAG7h+IwAAQIECBAgQIAAAQIECBDYhoAFgG20g6sgQIAAAQIECBAgQIAAAQJDBSwADOVVOQECBAgQIECAAAECBAgQ2IaABYBttIOrIECAAAECBAgQIECAAAECQwUsAAzlVTkBAgQIECBAgAABAgQIENiGgAWAbbSDqyBAgAABAgQIECBAgAABAkMFLAAM5VU5AQIECBAgQIAAAQIECBDYhoAFgG20g6sgQIAAAQIECBAgQIAAAQJDBSwADOVVOQECBAgQIECAAAECBAgQ2IaABYBttIOrIECAAAECBAgQIECAAAECQwUsAAzlVTkBAgQIECBAgAABAgQIENiGgAWAbbSDqyBAgAABAgQIECBAgAABAkMF/np4ePh7aIR3Kn98fHzniLEfv7y8jA3wTu3uX/u/84gM/djzL/+HPmDvVK7/0/+984gM/Vj/p/8b+oC9U7n+T//3ziMy9GP938/u/+wAGJpeKidAgAABAgQIECBAgAABAtsQsACwjXZwFQQIECBAgAABAgQIECBAYKiABYChvConQIAAAQIECBAgQIAAAQLbELAAsI12cBUECBAgQIAAAQIECBAgQGCogAWAobwqJ0CAAAECBAgQIECAAAEC2xCwALCNdnAVBAgQIECAAAECBAgQIEBgqIAFgKG8KidAgAABAgQIECBAgAABAtsQsACwjXZwFQQIECBAgAABAgQIECBAYKiABYChvConQIAAAQIECBAgQIAAAQLbELAAsI12cBUECBAgQIAAAQIECBAgQGCogAWAobwqJ0CAAAECBAgQIECAAAEC2xBYvQBwPt6V3snrfiqNf3w6l8bfT8X3f34qvf/XaV8a/3x3LI0/7YvvX/6Xtr/8L37+5X/p82/8n0r9j8b/Un/jf/Hz/9Pn/8X3X93/V3//XZv/qxcASns/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AAECBAjkClgAyPUWjQABAgQIECBAgAABAgQIlAhYAChhF5QAAQIECBAgQIAAAQIECOQKWADI9RaNAAECBAgQIECAAAECBAiUCFgAKGEXlAABAgQIECBAgAABAgQI5ApYAMj1Fo0AAQIECBAgQIAAAQIECJQIWAAoYReUAAECBAgQIECAAAECBAjkClgAyPUWjQABAgQIECBAgAABAgQIlAhYAChhF5QAAQIECBAgQIAAAQIECOQKWADI9RaNAAECBAgQIECAAAECBAiUCPz18PDwd0nk/wV9fHysDP+fl5eX0vjuX/tXPoCef/lf+fzp//R/lc+f/k//V/n86f/0f5XPn/7vZ/d/dgBUZp/YBAgQIECAAAECBAgQIEAgScACQBK0MAQIECBAgAABAgQIECBAoFLAAkClvtgECBAgQIAAAQIECBAgQCBJwAJAErQwBAgQIECAAAECBAgQIECgUsACQKW+2AQIECBAgAABAgQIECBAIEnAAkAStDAECBAgQIAAAQIECBAgQKBSwAJApb7YBAgQIECAAAECBAgQIEAgScACQBK0MAQIECBAgAABAgQIECBAoFLAAkClvtgECBAgQIAAAQIECBAgQCBJwAJAErQwBAgQIECAAAECBAgQIECgUsACQKW+2AQIECBAgAABAgQIECBAIElg9QLA8emcdKnXw+yn6foHSe8ez09Jka6HeZ321z9Ievd8d0yKdD3MtC++/+Pd9QtLevd1PyVFuh5G/sv/609Gzrvyf8qB/kMU+S////BopLwt/6cU5z8F+fH5X/z9p3r+dy6e/5bnf/H3n7Xf/1YvAPypY/A+AQIECBAgQIAAAQIECBAgsB0BCwDbaQtXQoAAAQIECBAgQIAAAQIEhgn8WlJzv+0nlg/PuyVVzjqn3/Yfy6eEnwT02/5j+bB7nnUvSw7ut33E8v10WlLlrHP6bf+xvHs7zKprycH9tp9Ynk4J999te4rboHaHtyW3NOucfttXLN+fpll1LTk45ns7P5blv/xf8kzNOSfmezsvluX/NIdy0bEx31sFsSz/5f+ih2rGSTHf22mxLP+nGZLLDo353mqI5R+R/922/+z7j/O95h/LGfO/ON9t8WM5Y/4b873Fj+WU/O+2/Wd//4nf99r9x/Lc73+LFgBikreHP5bbBY3+L37Jb1/+Y3l07FZ//JLfvvzHckb8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fN+2dWcECBAgQIAAAQIECBAgQOAisHoBIG4Hu9Sa+CJ7+39/a3E7UP9ZRjluh8qI18eI28H6zzLK2dv/+3uK24H6zzLKcTtkRrw+hvyfepLUsvyf95u3z24c+V/8/IftsJ/dth+pr3z8L77/6v6/evwrz//wc4CPPK+ffUz1/K96/JP/02c/UrPqk//r5j+rFwBmtZaDCRAgQIAAAQIECBAgQIAAgRIBCwAl7IISIECAAAECBAgQIECAAIFcAQsAud6iESBAgAABAgQIECBAgACBEgELACXsghIgQIAAAQIECBAgQIAAgVwBCwC53qIRIECAAAECBAgQIECAAIESAQsAJeyCEiBAgAABAgQIECBAgACBXAELALneohEgQIAAAQIECBAgQIAAgRIBCwAl7IISIECAAAECBAgQIECAAIFcAQsAud6iESBAgAABAgQIECBAgACBEgELACXsghIgQIAAAQIECBAgQIAAgVwBCwC53qIRIECAAAECBAgQIECAAIESAQsAJeyCEiBAgAABAgQIECBAgACBXIG/Hh4e/s4N+e9oj4+P/34jufTy8pIc8d/h3L/2//cTkVvy/Mv/3Cfu39H0f/q/fz8RuSX9n/4v94n7dzT9n/7v309Ebkn/97P7PzsAcvNNNAIECBAgQIAAAQIECBAgUCJgAaCEXVACBAgQIECAAAECBAgQIJArYAEg11s0AgQIECBAgAABAgQIECBQImABoIRdUAIECBAgQIAAAQIECBAgkCtgASDXWzQCBAgQIECAAAECBAgQIFAiYAGghF1QAgQIECBAgAABAgQIECCQK2ABINdbNAIECBAgQIAAAQIECBAgUCJgAaCEXVACBAgQIECAAAECBAgQIJArYAEg11s0AgQIECBAgAABAgQIECBQImABoIRdUAIECBAgQIAAAQIECBAgkCtgASDXWzQCBAgQIECAAAECBAgQIFAisHoB4Ph0Lrnwf4Lup+mflyX/P56fSuL+E/R12v/zsuT/57tjSdx/gk774vs/3v1zKSX/f91PJXH/CSr/5f8/z0LF/+X/VMF+ifnj8794/lPd/5+Lx7/y/C+e/1TP/6rnv+Xzf/l/GQsqXsj/dd9/Vi8AVDS6mAQIECBAgAABAgQIECBAgMA8AQsA87wcTYAAAQIECBAgQIAAAQIEvqTAryVX3W/7i+XD825JlbPO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+R/x//uG+G//3zz7yN/+P3b+j37+uXf/vzsB8NHk9B4BAgQIECBAgAABAgQIECAwh4AEwBzjoBcECBAgQIAAAQIECBAgQKCpgARAU16VEyBAgAABAgQIECBAgACBOQQkAOYYB70gQIAAAQIECBAgQIAAAQJNBSQAmvKqnAABAgQIECBAgAABAgQIzCEgATDHOOgFAQIECBAgQIAAAQIECBBoKiAB0JRX5QQIECBAgAABAgQIECBAYA4BCYA5xkEvCBAgQIAAAQIECBAgQIBAUwEJgKa8KidAgAABAgQIECBAgAABAnMISADMMQ56QYAAAQIECBAgQIAAAQIEmgpIADTlVTkBAgQIECBAgAABAgQIEJhDQAJgjnHQCwIECBAgQIAAAQIECBAg0FTg29PTU9MGPqv85eXls480fd/1G/+mE+yTys1/8f/JFGn6tvXP+td0gn1SufXP+vfJFGn6tvXP+td0gn1SufXP+vfJFGn6thMATXlVToAAAQIECBAgQIAAAQIE5hCQAJhjHPSCAAECBAgQIECAAAECBAg0FZAAaMqrcgIECBAgQIAAAQIECBAgMIeABMAc46AXBAgQIECAAAECBAgQIECgqYAEQFNelRMgQIAAAQIECBAgQIAAgTkEJADmGAe9IECAAAECBAgQIECAAAECTQUkAJryqpwAAQIECBAgQIAAAQIECMwhIAEwxzjoBQECBAgQIECAAAECBAgQaCogAdCUV+UECBAgQIAAAQIECBAgQGAOAQmAOcZBLwgQIECAAAECBAgQIECAQFMBCYCmvConQIAAAQIECBAgQIAAAQJzCNydADguy9ArOV+fh7b/uhyHtn99OA9tfzkOvv7zw9Drfz0uQ9s/P1+Htj88/gdf/+jxvw6e/8Pjf/D6N3r9H73/if+x67/4H3v/I/7Hzv/R9z/2/8H3//b/u+7/704A3NW6LxMgQIAAAQIECBAgQIAAAQJdBCQAujBrhAABAgQIECBAgAABAgQIjBX4tqX5+thfli8dfhJQH3vM8unwY8slrfpOfewry4/LZVVdWz5cH/vP8uHttKXKVd+pj/1mebl0uP7q2HMegzyc3lZdy5YP18e+svx4WbZUueo79bG3LJ9+HFbVteXDGe/l+1nuEv/Vsf/e15/jXa4/yz3GP+d7aT/LPeZ/xntpP8td4r869td7/cv1vlx/lnus/7nflfaz3GP/y3gv7WdZ/C+FpOlfxntpKMviv/39T8Z78c+y+F8KSdO/3O9LQ1nucf+T+31pP8v2/w73//b/Mu1uf/fs/5sSALnJl80/y7deNXyRNznl4rPcsNlb1bnIl8U/y7cPNXyRD/nl5jfLDZu9VZ03+eXmP8u3DzV8kTc55eYnyw2bvVWdi3xZ/LN8+1DDF7nJlc0vyw2bvVWd8T4k/iPJMeL6c7xHjH/O9xHzP+N9SPxHknPE+pfr/Yj1P/e7Efuf+F9ua6H4H7D/i//b/BP//e9/7P/vD/n2//7Pf3vu/34CcFtKvSBAgAABAgQIECBAgAABAr+vgATA7zu2rowAAQIECBAgQIAAAQIECNwE7k4A5HHAW60dX+RxiI7N3prK46C3f+z4ovfx//rS8jhw/V6Pch6H7tFe3UYeB6vf61Huffy/vqbh8R8/B6j71qM8evxHz//h8R/HgXuMd93G6PV/9P4n/pd6SnQti//2v/n/aEDF/9j5P/r+x/7//nOAj+Kk1Xujn3++evzfnQBoNbDqJUCAAAECBAgQIECAAAECBPYTkADYz1JNBAgQIECAAAECBAgQIEBgWgEJgGmHRscIECBAgAABAgQIECBAgMB+AhIA+1mqiQABAgQIECBAgAABAgQITCsgATDt0OgYAQIECBAgQIAAAQIECBDYT0ACYD9LNREgQIAAAQIECBAgQIAAgWkFJACmHRodI0CAAAECBAgQIECAAAEC+wlIAOxnqSYCBAgQIECAAAECBAgQIDCtgATAtEOjYwQIECBAgAABAgQIECBAYD8BCYD9LNVEgAABAgQIECBAgAABAgSmFZAAmHZodIwAAQIECBAgQIAAAQIECOwnIAGwn6WaCBAgQIAAAQIECBAgQIDAtAL//v79+39G9u7p6Wlk8/96eXkZ2r7rN/4jJ6D5L/5Hzj/rn/Vv5Pyz/ln/Rs4/65/1b+T8s/792eufEwAjo0/bBAgQIECAAAECBAgQIECgk4AEQCdozRAgQIAAAQIECBAgQIAAgZECEgAj9bVNgAABAgQIECBAgAABAgQ6CUgAdILWDAECBAgQIECAAAECBAgQGCkgATBSX9sECBAgQIAAAQIECBAgQKCTgARAJ2jNECBAgAABAgQIECBAgACBkQISACP1tU2AAAECBAgQIECAAAECBDoJSAB0gtYMAQIECBAgQIAAAQIECBAYKSABMFJf2wQIECBAgAABAgQIECBAoJOABEAnaM0QIECAAAECBAgQIECAAIGRAhIAI/W1TYAAAQIECBAgQIAAAQIEOgncnQA4X587dfXnzbwux5+/0elfrw/nTi39vJnlOPj6zw8/71inf309Lp1a+nkz5+frz9/o9K/HZenU0s+bGX39o8f/Onj+D4//wevf6PV/9P4n/seuf+J/7P2P+B87/+3/Y+9/7f9jn3+++v5/dwLg548F/pUAAQIECBAgQIAAAQIECBCYSUACYKbR0BcCBAgQIECAAAECBAgQINBI4NuWeutjD1k+HX5sqXLVd+pjX1l+XC6r6try4frYf5YPb6ctVa76Tn3sJ8vLpcP1V8ee8xjk4fS26lq2fLg+9p3lx8uypcpV36mPvWX59OOwqq4tH66P/Wb50uEnAXm9pf9Z7nH9Od6l/Sz3GP+c76X9LPeY/xnvpf0sd4n/6th/7/Uv1/ty/Vnusf7nflfaz3KP/S/jvbSfZfG/FJKmfxnvpaEsi//29z8Z78U/y+J/KSRN/3K/Lw1l2f7f/v439/vin2X7f/vnn9zv/3/+x8/gv9r+vykBkBdZMLJcQFr/5SJfFv8st2671J8P+eXmN8s92s8gL8Gf5R7t501OufnJco/28yGvPPxluUf7ucmVzS/LPdrPm/xy85/lHu3n9Y64/hzvEeOf833E/M94HxL/keQcsf7lej9i/c/9bsT+l/Eu/vuv/+L//SFf/Pe//xX/y+02x/4/4P7f/n+bf199//cTgNtQekGAAAECBAgQIECAAAECBH5fAQmA33dsXRkBAgQIECBAgAABAgQIELgJ3J0AyOOQt1o7vsjjoB2bvTXV+/j/reG/XuRx4Pq9HuU8DtmjvbqNPA5ev9ejnMfhe7RXt5HHAev3epRHX//o8R89/4fHfxwH7DHf6jZGr/+j9z/xv9RTomtZ/L//HKAr/F+Nif9lBPutTft/+9/837B/8sL+3/43/z9hv/3TV9//704A3CS8IECAAAECBAgQIECAAAECBKYVkACYdmh0jAABAgQIECBAgAABAgQI7CcgAbCfpZoIECBAgAABAgQIECBAgMC0AhIA0w6NjhEgQIAAAQIECBAgQIAAgf0EJAD2s1QTAQIECBAgQIAAAQIECBCYVkACYNqh0TECBAgQIECAAAECBAgQILCfgATAfpZqIkCAAAECBAgQIECAAAEC0wpIAEw7NDpGgAABAgQIECBAgAABAgT2E5AA2M9STQQIECBAgAABAgQIECBAYFoBCYBph0bHCBAgQIAAAQIECBAgQIDAfgISAPtZqokAAQIECBAgQIAAAQIECEwrIAEw7dDoGAECBAgQIECAAAECBAgQ2E/g29PT0361bajp5eVlw7f2+4rrN/77zab1NZn/4n/9rNnvG9Y/699+s2l9TdY/69/6WbPfN6x/1r/9ZtP6mqx/1r/1s2a/bzgBsJ+lmggQIECAAAECBAgQIECAwLQCEgDTDo2OESBAgAABAgQIECBAgACB/QQkAPazVBMBAgQIECBAgAABAgQIEJhWQAJg2qHRMQIECBAgQIAAAQIECBAgsJ+ABMB+lmoiQIAAAQIECBAgQIAAAQLTCkgATDs0OkaAAAECBAgQIECAAAECBPYTkADYz1JNBAgQIECAAAECBAgQIEBgWgEJgGmHRscIECBAgAABAgQIECBAgMB+AhIA+1mqiQABAgQIECBAgAABAgQITCsgATDt0OgYAQIECBAgQIAAAQIECBDYT0ACYD9LNREgQIAAAQIECBAgQIAAgWkF7k4AvC7HoRd3fTgPbX85Dr7+88PQ6389LkPbPz9fh7Z/XP7s6x89/tfB8394/A9e/0av/+frs/gfKCD+B+//4n/g7P/Xv+z/Y+9/7P9jn3/s/2Pn/73PP3cnAIauvhonQIAAAQIECBAgQIAAAQIEfklAAuCXmHyIAAECBAgQIECAAAECBAh8bYFvW7pfH/vI8uNy2VLlqu/Ux/6zfHg7rapry4frY79ZXi4drr869pzHoA6nty2XtOo79bHPLD9ellV1bflwfewly6cfhy1VrvpOfewvy5cOPwnI6y0dz3KP68/xLu1nucf453wv7We5x/zPeC/tZ7lL/FfHfnuvf7nel+vPco/1vz72n+XT4UfpUtO/jPfSUJbF/9LUvlSe8V6XxX/7+5+M9+KfZfG/FJKmf7nfl4aybP9vf/+b+33xz7L9v/3zT+73/z//42eA9v91zz+bEgC5yJfFP8tlQFr/5UN+ufnNcuu2S/0Z5CX4s9yj/bzJKTdDWe7Rfj7klYe/LPdoPze5svlluUf7eZNfbv6z3KP9vN4R15/jPWL8c76PmP8Z70PiP5KcI9a/XO9HrP+5yZebgSz3iL+Md/Hff/0X/+8P+eJf/Of9QI/1z/7//pBr/+///Jf7vf3/vucfPwHosWJqgwABAgQIECBAgAABAgQIDBaQABg8AJonQIAAAQIECBAgQIAAAQI9BO5OAORx0B4drtvoffy/bj+PA9fv9Sjnccge7dVt5HGw+r0e5d7H3+pryuPA9Xs9yqOvf/T4j57/w+M/fg7QY77VbYxe//M4YN23HmXxv/Rg/sc2xP/7zwH+EanhG+J/aaj7edX2//a/+f9oFOz/7z+H+Mip1Xv2/3W/+a/H4e4EQF2hMgECBAgQIECAAAECBAgQIDCfgATAfGOiRwQIECBAgAABAgQIECBAYHcBCYDdSVVIgAABAgQIECBAgAABAgTmE5AAmG9M9IgAAQIECBAgQIAAAQIECOwuIAGwO6kKCRAgQIAAAQIECBAgQIDAfAISAPONiR4RIECAAAECBAgQIECAAIHdBSQAdidVIQECBAgQIECAAAECBAgQmE9AAmC+MdEjAgQIECBAgAABAgQIECCwu4AEwO6kKiRAgAABAgQIECBAgAABAvMJSADMNyZ6RIAAAQIECBAgQIAAAQIEdheQANidVIUECBAgQIAAAQIECBAgQGA+AQmA+cZEjwgQIECAAAECBAgQICBcDOoAABxJSURBVECAwO4C//7+/ft/dq91RYVPT08rPr3/R19eXvavdEWNrt/4r5guu3/U/Bf/u0+qFRVa/6x/K6bL7h+1/ln/dp9UKyq0/ln/VkyX3T9q/fuz1z8nAHYPKRUSIECAAAECBAgQIECAAIH5BCQA5hsTPSJAgAABAgQIECBAgAABArsLSADsTqpCAgQIECBAgAABAgQIECAwn4AEwHxjokcECBAgQIAAAQIECBAgQGB3AQmA3UlVSIAAAQIECBAgQIAAAQIE5hOQAJhvTPSIAAECBAgQIECAAAECBAjsLiABsDupCgkQIECAAAECBAgQIECAwHwCEgDzjYkeESBAgAABAgQIECBAgACB3QUkAHYnVSEBAgQIECBAgAABAgQIEJhPQAJgvjHRIwIECBAgQIAAAQIECBAgsLuABMDupCokQIAAAQIECBAgQIAAAQLzCdydALg+nIde1XI8Dm3/en4Y2v7rcRna/vn5OrT94/JnX//o8R89/4fH/+D173UZu/6dr8/if6CA+B87/0ff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/+vufzclAPImpyx+WW49+KX+HOQy+Fnu0X4+5JaH3yz3aD83+bL5Z7lH+3m9I64/x3vE+Od8HzH/8yG/PPxnucf45yZfHv6z3KP9fMgvD/9Z7tF+PuSXh/8s92g/413891//xf/7Q774F/95P9Bj/bP/v9/k2//t/3k/0CP+Mt7d//d//s37/Xvj308AekSMNggQIECAAAECBAgQIECAwGABCYDBA6B5AgQIECBAgAABAgQIECDQQ+DuBEAeh+zR4bqNPA5Wv9ejnMdherRXt9H7+E/d/ujrHz3+o+d/Hgeqx6ZHuffx//qaeh//r9vvffy/bl/8LzVJ17L4f/85QFf4vxoT/8sI9lub9v/2/82rG/ZPXtj/338O8ROe5v9k/2//3zz7aBDd/983/+9OAHw0ON4jQIAAAQIECBAgQIAAAQIE5hCQAJhjHPSCAAECBAgQIECAAAECBAg0FZAAaMqrcgIECBAgQIAAAQIECBAgMIeABMAc46AXBAgQIECAAAECBAgQIECgqYAEQFNelRMgQIAAAQIECBAgQIAAgTkEJADmGAe9IECAAAECBAgQIECAAAECTQUkAJryqpwAAQIECBAgQIAAAQIECMwhIAEwxzjoBQECBAgQIECAAAECBAgQaCogAdCUV+UECBAgQIAAAQIECBAgQGAOAQmAOcZBLwgQIECAAAECBAgQIECAQFMBCYCmvConQIAAAQIECBAgQIAAAQJzCEgAzDEOekGAAAECBAgQIECAAAECBJoKfHt6emrawGeVv7y8fPaRpu+7fuPfdIJ9Urn5L/4/mSJN37b+Wf+aTrBPKrf+Wf8+mSJN37b+Wf+aTrBPKrf+Wf8+mSJN33YCoCmvygkQIECAAAECBAgQIECAwBwCEgBzjINeECBAgAABAgQIECBAgACBpgISAE15VU6AAAECBAgQIECAAAECBOYQkACYYxz0ggABAgQIECBAgAABAgQINBWQAGjKq3ICBAgQIECAAAECBAgQIDCHgATAHOOgFwQIECBAgAABAgQIECBAoKmABEBTXpUTIECAAAECBAgQIECAAIE5BCQA5hgHvSBAgAABAgQIECBAgAABAk0FJACa8qqcAAECBAgQIECAAAECBAjMISABMMc46AUBAgQIECBAgAABAgQIEGgqIAHQlFflBAgQIECAAAECBAgQIEBgDoG7EwCv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" + } + ] +} diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index f845d71..16ef3ac 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -75,7 +75,8 @@ async fn main() { let mut resman = world.get_resource_mut::().unwrap(); let diffuse_texture = resman.request::("assets/happy-tree.png").unwrap(); - let cube_model = resman.request::("assets/cube-embedded.gltf").unwrap(); + //let cube_model = resman.request::("assets/cube-embedded.gltf").unwrap(); + let cube_model = resman.request::("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::>(); if keys.is_none() { return Ok(()); } + let keys = keys.unwrap(); - let keys: Ref> = 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)); } diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index 1dda54f..ae214bf 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -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" diff --git a/lyra-resource/src/lib.rs b/lyra-resource/src/lib.rs index ccdbdc1..f78b8ab 100644 --- a/lyra-resource/src/lib.rs +++ b/lyra-resource/src/lib.rs @@ -14,4 +14,6 @@ pub mod model; pub use model::*; pub mod material; -pub use material::*; \ No newline at end of file +pub use material::*; + +pub(crate) mod util; \ No newline at end of file diff --git a/lyra-resource/src/loader/texture.rs b/lyra-resource/src/loader/image.rs similarity index 53% rename from lyra-resource/src/loader/texture.rs rename to lyra-resource/src/loader/image.rs index 66e5c17..8a1940b 100644 --- a/lyra-resource/src/loader/texture.rs +++ b/lyra-resource/src/loader/image.rs @@ -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 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, 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, 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, offset: usize, length: usize) -> Result, 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()); } } \ No newline at end of file diff --git a/lyra-resource/src/loader/mod.rs b/lyra-resource/src/loader/mod.rs index 1b26c7b..06d13df 100644 --- a/lyra-resource/src/loader/mod.rs +++ b/lyra-resource/src/loader/mod.rs @@ -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 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, 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, LoaderError>; + /// Load a resource from bytes. + fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec, offset: usize, length: usize) -> Result, 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 = extensions.iter().map(|e| format!("a.{}", e)).collect(); for path in fake_paths.iter() { diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index a1cb9e0..e2d2b6d 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -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 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 for LoaderError { + fn from(value: ModelLoaderError) -> Self { + LoaderError::DecodingError(value.into()) + } +} + #[derive(Default)] pub struct ModelLoader; impl ModelLoader { - fn parse_uri(uri: &str) -> Option> { + /* fn parse_uri(containing_path: &str, uri: &str) -> Option> { 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>, materials: &Vec, node: gltf::Node<'_>) -> Vec { 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 = tex_coords.into_f32().map(|t| t.into()).collect(); // TODO: u16, u8 + let tex_coords: Vec = 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, crate::LoaderError> { + fn mime_types(&self) -> &[&str] { + &[] + } + + fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result, 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> = 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 = gltf.materials() - .map(|mat| Material::from(mat)).collect(); + .map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect(); let meshes: Vec = 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, offset: usize, length: usize) -> Result, 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::>(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 } } \ No newline at end of file diff --git a/lyra-resource/src/material.rs b/lyra-resource/src/material.rs index 7ef227d..252b2e1 100644 --- a/lyra-resource/src/material.rs +++ b/lyra-resource/src/material.rs @@ -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> 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> 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 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, pub name: Option, pub double_sided: bool, - pub pbr_roughness: PbrRoughness, - pub pbr_glossiness: Option, - pub alpha_cutoff: Option, - pub alpha_mode: gltf::material::AlphaMode, + + //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>, - pub texture: Option>, + /// 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>, + + /// 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, + + /// The optional alpha cutoff value of the material. + pub alpha_cutoff: Option, + + /// 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>, } -impl From> 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 { + 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, 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 { + // 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::(&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, } } } \ No newline at end of file diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index 652cef2..9484232 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -10,6 +10,15 @@ pub enum MeshIndices { U32(Vec), } +impl MeshIndices { + pub fn len(&self) -> usize { + match self { + MeshIndices::U16(v) => v.len(), + MeshIndices::U32(v) => v.len(), + } + } +} + /* impl From> for MeshIndices { fn from(value: Vec) -> Self { MeshIndices::U8(value) diff --git a/lyra-resource/src/resource_manager.rs b/lyra-resource/src/resource_manager.rs index 0204dee..b334429 100644 --- a/lyra-resource/src/resource_manager.rs +++ b/lyra-resource/src/resource_manager.rs @@ -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 for RequestError { @@ -39,16 +43,19 @@ impl From for RequestError { } } +/// A struct that stores all Manager data. This is requried for sending +//struct ManagerStorage + pub struct ResourceManager { resources: HashMap>, - loaders: Vec>, + loaders: Vec>, } 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 to Arc + // cast Arc to Arc let res = res.as_arc_any(); let res = res.downcast::>() .expect("Failure to downcast resource! Does the loader return an `Arc>`?"); @@ -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(&mut self, ident: &str, mime_type: &str, bytes: Vec, offset: usize, length: usize) -> Result>, 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 to Arc + let res = res.as_arc_any(); + let res = res.downcast::>() + .expect("Failure to downcast resource! Does the loader return an `Arc>`?"); + + Ok(res) + } else { + Err(RequestError::UnsupportedMime(mime_type.to_string())) + } + } + + /// Requests bytes from the manager. + pub fn request_loaded_bytes(&mut self, ident: &str) -> Result>, RequestError> { + match self.resources.get(&ident.to_string()) { + Some(res) => { + let res = res.clone().as_arc_any(); + let res = res.downcast::>().expect("Failure to downcast resource"); + + Ok(res) + }, + None => { + Err(RequestError::IdentNotFound(ident.to_string())) + } + } + } } #[cfg(test)] diff --git a/lyra-resource/src/util.rs b/lyra-resource/src/util.rs new file mode 100644 index 0000000..e7202df --- /dev/null +++ b/lyra-resource/src/util.rs @@ -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, 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)) + } +} \ No newline at end of file diff --git a/lyra-resource/test_files/gltf/texture-bin.glb b/lyra-resource/test_files/gltf/texture-bin.glb new file mode 100644 index 0000000000000000000000000000000000000000..9a3965b8b9db94f9ad5928dd8f87e69d35f839b7 GIT binary patch literal 42320 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 QoBfmHkBL95`h`aOKgU;APyhe` literal 0 HcmV?d00001 diff --git a/lyra-resource/test_files/gltf/texture-embedded.gltf b/lyra-resource/test_files/gltf/texture-embedded.gltf new file mode 100644 index 0000000..5309758 --- /dev/null +++ b/lyra-resource/test_files/gltf/texture-embedded.gltf @@ -0,0 +1,142 @@ +{ + "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":[ + { + "bufferView":4, + "mimeType":"image/png", + "name":"uvgrid" + } + ], + "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 + }, + { + "buffer":0, + "byteLength":40222, + "byteOffset":840 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":41064, + "uri":"data:application/octet-stream;base64,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xn/zf/P/8f/oF5M6zvfN/9fN//0EID5ZXhMgQIAAAQIECBAgQIAAgW8qYAHgmzas2yJAgAABAgQIECBAgAABAlFg9QJA3A4cK856nb39v7+v7O0/ffzs7X99/Oztv338uB2o/yyjHLcDZsTrY8TtwP1nGWX5P/43f7faUf5Pt3iGfyb/x//Nn1uNaPwf/zd/bvkb/3N//tO3hfG/+PlP+JtffZvHsvF/ihzpr7/6+L96ASBdXEACBAgQIECAAAECBAgQIEBgtoAFgNlkTiBAgAABAgQIECBAgAABAl9PwALA12szV0yAAAECBAgQIECAAAECBGYLWACYTeYEAgQIECBAgAABAgQIECDw9QQsAHy9NnPFBAgQIECAAAECBAgQIEBgtoAFgNlkTiBAgAABAgQIECBAgAABAl9PwALA12szV0yAAAECBAgQIECAAAECBGYLWACYTeYEAgQIECBAgAABAgQIECDw9QQsAHy9NnPFBAgQIECAAAECBAgQIEBgtoAFgNlkTiBAgAABAgQIECBAgAABAl9PwALA12szV0yAAAECBAgQIECAAAECBGYLWACYTeYEAgQIECBAgAABAgQIECDw9QR+PT4+ll71y8tLaXz3r/0rH0DPv/yvfP70f/q/yudP/6f/q3z+9H/6v8rnT/+n/6t8/uwAqNQXmwABAgQIECBAgAABAgQIJAlYAEiCFoYAAQIECBAgQIAAAQIECFQKWACo1BebAAECBAgQIECAAAECBAgkCVgASIIWhgABAgQIECBAgAABAgQIVApYAKjUF5sAAQIECBAgQIAAAQIECCQJWABIghaGAAECBAgQIECAAAECBAhUClgAqNQXmwABAgQIECBAgAABAgQIJAlYAEiCFoYAAQIECBAgQIAAAQIECFQKWACo1BebAAECBAgQIECAAAECBAgkCVgASIIWhgABAgQIECBAgAABAgQIVApYAKjUF5sAAQIECBAgQIAAAQIECCQJrF4AeJ32SZd6Pcz57nj9g6R3p33x/R/vku70epjX/XT9g6R3j0/npEjXw+yn6foHSe8ez09Jka6Hkf/y//qTkfOu/Jf/OU/a9SjG/+k6TNK78l/+Jz1qV8PI/+mqS9abPz7/V37/Wb0AkNXQ4hAgQIAAAQIECBAgQIAAAQLLBSwALLdzJgECBAgQIECAAAECBAgQ+DICv5Zc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xn/j//gv/TH/4nhv/Df/N/+fYnoMfx37u4rvP7G9K77/xe+7a7//+gnA8MdVAAIECBAgQIAAAQIECBAgUC9gAaC+DVwBAQIECBAgQIAAAQIECBAYLrB6ASBuBx5+tVcCxO0QVz4e/lbcDjI82JUAcTvMlY+Hv5W9/be/oezt/338uB2w/yyjHLcDZ8TrY8j/qSdJLcv/3O2/fePK/9yfP/T+xv/xf/OnN49l43/x85/wN39ie/evjf9TT5Ja/vHjf8LfPLvVoNXf/9aOf6sXAG7h+IwAAQIECBAgQIAAAQIECBDYhoAFgG20g6sgQIAAAQIECBAgQIAAAQJDBSwADOVVOQECBAgQIECAAAECBAgQ2IaABYBttIOrIECAAAECBAgQIECAAAECQwUsAAzlVTkBAgQIECBAgAABAgQIENiGgAWAbbSDqyBAgAABAgQIECBAgAABAkMFLAAM5VU5AQIECBAgQIAAAQIECBDYhoAFgG20g6sgQIAAAQIECBAgQIAAAQJDBSwADOVVOQECBAgQIECAAAECBAgQ2IaABYBttIOrIECAAAECBAgQIECAAAECQwUsAAzlVTkBAgQIECBAgAABAgQIENiGgAWAbbSDqyBAgAABAgQIECBAgAABAkMF/np4ePh7aIR3Kn98fHzniLEfv7y8jA3wTu3uX/u/84gM/djzL/+HPmDvVK7/0/+984gM/Vj/p/8b+oC9U7n+T//3ziMy9GP938/u/+wAGJpeKidAgAABAgQIECBAgAABAtsQsACwjXZwFQQIECBAgAABAgQIECBAYKiABYChvConQIAAAQIECBAgQIAAAQLbELAAsI12cBUECBAgQIAAAQIECBAgQGCogAWAobwqJ0CAAAECBAgQIECAAAEC2xCwALCNdnAVBAgQIECAAAECBAgQIEBgqIAFgKG8KidAgAABAgQIECBAgAABAtsQsACwjXZwFQQIECBAgAABAgQIECBAYKiABYChvConQIAAAQIECBAgQIAAAQLbELAAsI12cBUECBAgQIAAAQIECBAgQGCogAWAobwqJ0CAAAECBAgQIECAAAEC2xBYvQBwPt6V3snrfiqNf3w6l8bfT8X3f34qvf/XaV8a/3x3LI0/7YvvX/6Xtr/8L37+5X/p82/8n0r9j8b/Un/jf/Hz/9Pn/8X3X93/V3//XZv/qxcASns/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AAECBAjkClgAyPUWjQABAgQIECBAgAABAgQIlAhYAChhF5QAAQIECBAgQIAAAQIECOQKWADI9RaNAAECBAgQIECAAAECBAiUCFgAKGEXlAABAgQIECBAgAABAgQI5ApYAMj1Fo0AAQIECBAgQIAAAQIECJQIWAAoYReUAAECBAgQIECAAAECBAjkClgAyPUWjQABAgQIECBAgAABAgQIlAhYAChhF5QAAQIECBAgQIAAAQIECOQKWADI9RaNAAECBAgQIECAAAECBAiUCPz18PDwd0nk/wV9fHysDP+fl5eX0vjuX/tXPoCef/lf+fzp//R/lc+f/k//V/n86f/0f5XPn/7vZ/d/dgBUZp/YBAgQIECAAAECBAgQIEAgScACQBK0MAQIECBAgAABAgQIECBAoFLAAkClvtgECBAgQIAAAQIECBAgQCBJwAJAErQwBAgQIECAAAECBAgQIECgUsACQKW+2AQIECBAgAABAgQIECBAIEnAAkAStDAECBAgQIAAAQIECBAgQKBSwAJApb7YBAgQIECAAAECBAgQIEAgScACQBK0MAQIECBAgAABAgQIECBAoFLAAkClvtgECBAgQIAAAQIECBAgQCBJwAJAErQwBAgQIECAAAECBAgQIECgUsACQKW+2AQIECBAgAABAgQIECBAIElg9QLA8emcdKnXw+yn6foHSe8ez09Jka6HeZ321z9Ievd8d0yKdD3MtC++/+Pd9QtLevd1PyVFuh5G/sv/609Gzrvyf8qB/kMU+S////BopLwt/6cU5z8F+fH5X/z9p3r+dy6e/5bnf/H3n7Xf/1YvAPypY/A+AQIECBAgQIAAAQIECBAgsB0BCwDbaQtXQoAAAQIECBAgQIAAAQIEhgn8WlJzv+0nlg/PuyVVzjqn3/Yfy6eEnwT02/5j+bB7nnUvSw7ut33E8v10WlLlrHP6bf+xvHs7zKprycH9tp9Ynk4J999te4rboHaHtyW3NOucfttXLN+fpll1LTk45ns7P5blv/xf8kzNOSfmezsvluX/NIdy0bEx31sFsSz/5f+ih2rGSTHf22mxLP+nGZLLDo353mqI5R+R/922/+z7j/O95h/LGfO/ON9t8WM5Y/4b873Fj+WU/O+2/Wd//4nf99r9x/Lc73+LFgBikreHP5bbBY3+L37Jb1/+Y3l07FZ//JLfvvzHckb8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fN+2dWcECBAgQIAAAQIECBAgQOAisHoBIG4Hu9Sa+CJ7+39/a3E7UP9ZRjluh8qI18eI28H6zzLK2dv/+3uK24H6zzLKcTtkRrw+hvyfepLUsvyf95u3z24c+V/8/IftsJ/dth+pr3z8L77/6v6/evwrz//wc4CPPK+ffUz1/K96/JP/02c/UrPqk//r5j+rFwBmtZaDCRAgQIAAAQIECBAgQIAAgRIBCwAl7IISIECAAAECBAgQIECAAIFcAQsAud6iESBAgAABAgQIECBAgACBEgELACXsghIgQIAAAQIECBAgQIAAgVwBCwC53qIRIECAAAECBAgQIECAAIESAQsAJeyCEiBAgAABAgQIECBAgACBXAELALneohEgQIAAAQIECBAgQIAAgRIBCwAl7IISIECAAAECBAgQIECAAIFcAQsAud6iESBAgAABAgQIECBAgACBEgELACXsghIgQIAAAQIECBAgQIAAgVwBCwC53qIRIECAAAECBAgQIECAAIESAQsAJeyCEiBAgAABAgQIECBAgACBXIG/Hh4e/s4N+e9oj4+P/34jufTy8pIc8d/h3L/2//cTkVvy/Mv/3Cfu39H0f/q/fz8RuSX9n/4v94n7dzT9n/7v309Ebkn/97P7PzsAcvNNNAIECBAgQIAAAQIECBAgUCJgAaCEXVACBAgQIECAAAECBAgQIJArYAEg11s0AgQIECBAgAABAgQIECBQImABoIRdUAIECBAgQIAAAQIECBAgkCtgASDXWzQCBAgQIECAAAECBAgQIFAiYAGghF1QAgQIECBAgAABAgQIECCQK2ABINdbNAIECBAgQIAAAQIECBAgUCJgAaCEXVACBAgQIECAAAECBAgQIJArYAEg11s0AgQIECBAgAABAgQIECBQImABoIRdUAIECBAgQIAAAQIECBAgkCtgASDXWzQCBAgQIECAAAECBAgQIFAisHoB4Ph0Lrnwf4Lup+mflyX/P56fSuL+E/R12v/zsuT/57tjSdx/gk774vs/3v1zKSX/f91PJXH/CSr/5f8/z0LF/+X/VMF+ifnj8794/lPd/5+Lx7/y/C+e/1TP/6rnv+Xzf/l/GQsqXsj/dd9/Vi8AVDS6mAQIECBAgAABAgQIECBAgMA8AQsA87wcTYAAAQIECBAgQIAAAQIEvqTAryVX3W/7i+XD825JlbPO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+R/x//uG+G//3zz7yN/+P3b+j37+uXf/vzsB8NHk9B4BAgQIECBAgAABAgQIECAwh4AEwBzjoBcECBAgQIAAAQIECBAgQKCpgARAU16VEyBAgAABAgQIECBAgACBOQQkAOYYB70gQIAAAQIECBAgQIAAAQJNBSQAmvKqnAABAgQIECBAgAABAgQIzCEgATDHOOgFAQIECBAgQIAAAQIECBBoKiAB0JRX5QQIECBAgAABAgQIECBAYA4BCYA5xkEvCBAgQIAAAQIECBAgQIBAUwEJgKa8KidAgAABAgQIECBAgAABAnMISADMMQ56QYAAAQIECBAgQIAAAQIEmgpIADTlVTkBAgQIECBAgAABAgQIEJhDQAJgjnHQCwIECBAgQIAAAQIECBAg0FTg29PTU9MGPqv85eXls480fd/1G/+mE+yTys1/8f/JFGn6tvXP+td0gn1SufXP+vfJFGn6tvXP+td0gn1SufXP+vfJFGn6thMATXlVToAAAQIECBAgQIAAAQIE5hCQAJhjHPSCAAECBAgQIECAAAECBAg0FZAAaMqrcgIECBAgQIAAAQIECBAgMIeABMAc46AXBAgQIECAAAECBAgQIECgqYAEQFNelRMgQIAAAQIECBAgQIAAgTkEJADmGAe9IECAAAECBAgQIECAAAECTQUkAJryqpwAAQIECBAgQIAAAQIECMwhIAEwxzjoBQECBAgQIECAAAECBAgQaCogAdCUV+UECBAgQIAAAQIECBAgQGAOAQmAOcZBLwgQIECAAAECBAgQIECAQFMBCYCmvConQIAAAQIECBAgQIAAAQJzCNydADguy9ArOV+fh7b/uhyHtn99OA9tfzkOvv7zw9Drfz0uQ9s/P1+Htj88/gdf/+jxvw6e/8Pjf/D6N3r9H73/if+x67/4H3v/I/7Hzv/R9z/2/8H3//b/u+7/704A3NW6LxMgQIAAAQIECBAgQIAAAQJdBCQAujBrhAABAgQIECBAgAABAgQIjBX4tqX5+thfli8dfhJQH3vM8unwY8slrfpOfewry4/LZVVdWz5cH/vP8uHttKXKVd+pj/1mebl0uP7q2HMegzyc3lZdy5YP18e+svx4WbZUueo79bG3LJ9+HFbVteXDGe/l+1nuEv/Vsf/e15/jXa4/yz3GP+d7aT/LPeZ/xntpP8td4r869td7/cv1vlx/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XO9HrP+5yZebgSz3iL+Md/Hff/0X/+8P+eJf/Of9QI/1z/7//pBr/+///Jf7vf3/vucfPwHosWJqgwABAgQIECBAgAABAgQIDBaQABg8AJonQIAAAQIECBAgQIAAAQI9BO5OAORx0B4drtvoffy/bj+PA9fv9Sjnccge7dVt5HGw+r0e5d7H3+pryuPA9Xs9yqOvf/T4j57/w+M/fg7QY77VbYxe//M4YN23HmXxv/Rg/sc2xP/7zwH+EanhG+J/aaj7edX2//a/+f9oFOz/7z+H+Mip1Xv2/3W/+a/H4e4EQF2hMgECBAgQIECAAAECBAgQIDCfgATAfGOiRwQIECBAgAABAgQIECBAYHcBCYDdSVVIgAABAgQIECBAgAABAgTmE5AAmG9M9IgAAQIECBAgQIAAAQIECOwuIAGwO6kKCRAgQIAAAQIECBAgQIDAfAISAPONiR4RIECAAAECBAgQIECAAIHdBSQAdidVIQECBAgQIECAAAECBAgQmE9AAmC+MdEjAgQIECBAgAABAgQIECCwu4AEwO6kKiRAgAABAgQIECBAgAABAvMJSADMNyZ6RIAAAQIECBAgQIAAAQIEdheQANidVIUECBAgQIAAAQIECBAgQGA+AQmA+cZEjwgQIECAAAECBAgQICBcDOoAABxJSURBVECAwO4C//7+/ft/dq91RYVPT08rPr3/R19eXvavdEWNrt/4r5guu3/U/Bf/u0+qFRVa/6x/K6bL7h+1/ln/dp9UKyq0/ln/VkyX3T9q/fuz1z8nAHYPKRUSIECAAAECBAgQIECAAIH5BCQA5hsTPSJAgAABAgQIECBAgAABArsLSADsTqpCAgQIECBAgAABAgQIECAwn4AEwHxjokcECBAgQIAAAQIECBAgQGB3AQmA3UlVSIAAAQIECBAgQIAAAQIE5hOQAJhvTPSIAAECBAgQIECAAAECBAjsLiABsDupCgkQIECAAAECBAgQIECAwHwCEgDzjYkeESBAgAABAgQIECBAgACB3QUkAHYnVSEBAgQIECBAgAABAgQIEJhPQAJgvjHRIwIECBAgQIAAAQIECBAgsLuABMDupCokQIAAAQIECBAgQIAAAQLzCdydALg+nIde1XI8Dm3/en4Y2v7rcRna/vn5OrT94/JnX//o8R89/4fH/+D173UZu/6dr8/if6CA+B87/0ff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/+vufzclAPImpyx+WW49+KX+HOQy+Fnu0X4+5JaH3yz3aD83+bL5Z7lH+3m9I64/x3vE+Od8HzH/8yG/PPxnucf45yZfHv6z3KP9fMgvD/9Z7tF+PuSXh/8s92g/413891//xf/7Q774F/95P9Bj/bP/v9/k2//t/3k/0CP+Mt7d//d//s37/Xvj308AekSMNggQIECAAAECBAgQIECAwGABCYDBA6B5AgQIECBAgAABAgQIECDQQ+DuBEAeh+zR4bqNPA5Wv9ejnMdherRXt9H7+E/d/ujrHz3+o+d/Hgeqx6ZHuffx//qaeh//r9vvffy/bl/8LzVJ17L4f/85QFf4vxoT/8sI9lub9v/2/82rG/ZPXtj/338O8ROe5v9k/2//3zz7aBDd/983/+9OAHw0ON4jQIAAAQIECBAgQIAAAQIE5hCQAJhjHPSCAAECBAgQIECAAAECBAg0FZAAaMqrcgIECBAgQIAAAQIECBAgMIeABMAc46AXBAgQIECAAAECBAgQIECgqYAEQFNelRMgQIAAAQIECBAgQIAAgTkEJADmGAe9IECAAAECBAgQIECAAAECTQUkAJryqpwAAQIECBAgQIAAAQIECMwhIAEwxzjoBQECBAgQIECAAAECBAgQaCogAdCUV+UECBAgQIAAAQIECBAgQGAOAQmAOcZBLwgQIECAAAECBAgQIECAQFMBCYCmvConQIAAAQIECBAgQIAAAQJzCEgAzDEOekGAAAECBAgQIECAAAECBJoKfHt6emrawGeVv7y8fPaRpu+7fuPfdIJ9Urn5L/4/mSJN37b+Wf+aTrBPKrf+Wf8+mSJN37b+Wf+aTrBPKrf+Wf8+mSJN33YCoCmvygkQIECAAAECBAgQIECAwBwCEgBzjINeECBAgAABAgQIECBAgACBpgISAE15VU6AAAECBAgQIECAAAECBOYQkACYYxz0ggABAgQIECBAgAABAgQINBWQAGjKq3ICBAgQIECAAAECBAgQIDCHgATAHOOgFwQIECBAgAABAgQIECBAoKmABEBTXpUTIECAAAECBAgQIECAAIE5BCQA5hgHvSBAgAABAgQIECBAgAABAk0FJACa8qqcAAECBAgQIECAAAECBAjMISABMMc46AUBAgQIECBAgAABAgQIEGgqIAHQlFflBAgQIECAAAECBAgQIEBgDoG7EwCv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" + } + ] +} diff --git a/lyra-resource/test_files/gltf/texture-sep/Green.png b/lyra-resource/test_files/gltf/texture-sep/Green.png new file mode 100644 index 0000000000000000000000000000000000000000..fd1076ef27db240de0b87b82536601c42dfe125c GIT binary patch literal 5735 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4YzkrlkYJ_K+udf!6$shs59ze_p zrVG*&8-;32ZLK$+s*+QoCO|{KtXOH5N5n|x9$%E zgXkeo7srqa#<$lT1sN20m>nIMx<2G{rEMsTX21DmV&%JihXoZJ8W5a=m(3kG%>L`Y$%z?DF9T}!1#b2DC^MR5O9EjW15~3ShWGeAE0)iHV!Tc zhJ%+})xa9r8ss77Ix;dVMC>{NGS=Y$!v|(CLqQ{ffyG;VCRn2cLmkKgEF3JXJPipi zm4v|>nGf)TtYTcc0cKd)e@-WzsI(9TbP0l+XkK3&4a< literal 0 HcmV?d00001 diff --git a/lyra-resource/test_files/gltf/texture-sep/texture-sep.bin b/lyra-resource/test_files/gltf/texture-sep/texture-sep.bin new file mode 100644 index 0000000000000000000000000000000000000000..9322438cd45aec0d8a555a9fbe532cd726c3f498 GIT binary patch literal 840 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 literal 0 HcmV?d00001 diff --git a/src/render/renderer.rs b/src/render/renderer.rs index f51bde1..345a303 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -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 = 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 = 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(); diff --git a/src/render/shaders/base.wgsl b/src/render/shaders/base.wgsl index 6361232..c8e6ce1 100755 --- a/src/render/shaders/base.wgsl +++ b/src/render/shaders/base.wgsl @@ -2,7 +2,7 @@ struct VertexInput { @location(0) position: vec3, - //@location(1) tex_coords: vec2, + @location(1) tex_coords: vec2, } struct VertexOutput { @@ -25,7 +25,7 @@ fn vs_main( model: VertexInput, ) -> VertexOutput { var out: VertexOutput; - out.tex_coords = vec2(1.0, 1.0); + out.tex_coords = model.tex_coords; out.clip_position = camera.view_proj * u_model_transform * vec4(model.position, 1.0); return out; } diff --git a/src/render/vertex.rs b/src/render/vertex.rs index 0f18f77..2b53e9d 100755 --- a/src/render/vertex.rs +++ b/src/render/vertex.rs @@ -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::() as wgpu::BufferAddress, + array_stride: std::mem::size_of::() 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 + } ] } } From 7ae59c04154ce47699d1444007da1c50c1673750 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 21 Oct 2023 22:19:34 -0400 Subject: [PATCH 13/14] Loading textures from gltf blob and gltf.bin's, fix loading multiple meshses in a single model --- Cargo.lock | 44 +++++- Cargo.toml | 1 + examples/testbed/Cargo.lock | 44 +++++- examples/testbed/assets/AntiqueCamera.glb | Bin 0 -> 20199648 bytes examples/testbed/assets/cube-texture-bin.glb | Bin 0 -> 42320 bytes .../assets/texture-sep/texture-sep.bin | Bin 0 -> 840 bytes .../assets/texture-sep/texture-sep.gltf | 137 ++++++++++++++++++ .../testbed/assets/texture-sep/uvgrid.png | Bin 0 -> 40222 bytes examples/testbed/src/main.rs | 32 +--- lyra-resource/src/loader/model.rs | 50 ++++++- lyra-resource/src/material.rs | 32 ++-- lyra-resource/src/model.rs | 14 +- lyra-resource/src/util.rs | 27 ++-- .../test_files/gltf/texture-sep/Green.png | Bin 5735 -> 0 bytes rust-toolchain | 1 + src/game.rs | 2 +- src/lib.rs | 2 + src/render/render_job.rs | 41 +----- src/render/renderer.rs | 119 +++++++++------ 19 files changed, 399 insertions(+), 147 deletions(-) create mode 100644 examples/testbed/assets/AntiqueCamera.glb create mode 100644 examples/testbed/assets/cube-texture-bin.glb create mode 100644 examples/testbed/assets/texture-sep/texture-sep.bin create mode 100644 examples/testbed/assets/texture-sep/texture-sep.gltf create mode 100644 examples/testbed/assets/texture-sep/uvgrid.png delete mode 100644 lyra-resource/test_files/gltf/texture-sep/Green.png create mode 100644 rust-toolchain diff --git a/Cargo.lock b/Cargo.lock index 7a09e3c..4e6e011 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -1294,6 +1294,7 @@ dependencies = [ "tracing-appender", "tracing-log", "tracing-subscriber", + "uuid", "wgpu", "winit", ] @@ -1552,7 +1553,7 @@ checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" dependencies = [ "num-integer", "num-traits", - "rand", + "rand 0.4.6", "rustc-serialize", ] @@ -1867,6 +1868,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-easy" version = "0.3.0" @@ -1934,6 +1941,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1949,6 +1977,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.3" @@ -2483,11 +2520,12 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "uuid" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom", + "rand 0.8.5", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4f755b4..1aaf3ca 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ aligned-vec = "0.5.0" tracing-appender = "0.2.2" stopwatch = "0.0.7" petgraph = "0.6.4" +uuid = { version = "1.5.0", features = ["v4", "fast-rng"] } \ No newline at end of file diff --git a/examples/testbed/Cargo.lock b/examples/testbed/Cargo.lock index 3893931..f7dfad3 100644 --- a/examples/testbed/Cargo.lock +++ b/examples/testbed/Cargo.lock @@ -1333,6 +1333,7 @@ dependencies = [ "tracing-appender", "tracing-log", "tracing-subscriber", + "uuid", "wgpu", "winit", ] @@ -1581,7 +1582,7 @@ checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" dependencies = [ "num-integer", "num-traits", - "rand", + "rand 0.4.6", "rustc-serialize", ] @@ -1877,6 +1878,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-easy" version = "0.3.0" @@ -1944,6 +1951,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1959,6 +1987,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.3" @@ -2508,11 +2545,12 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "uuid" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom", + "rand 0.8.5", ] [[package]] diff --git a/examples/testbed/assets/AntiqueCamera.glb b/examples/testbed/assets/AntiqueCamera.glb new file mode 100644 index 0000000000000000000000000000000000000000..a45f1343aff97999cfea0665c75be2e012bdff00 GIT binary patch literal 20199648 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