Implement loading material textures from gltf and rendering them
This commit is contained in:
parent
02a0eea7b3
commit
fd9f4bee2a
|
@ -1091,6 +1091,12 @@ dependencies = [
|
||||||
"hashbrown 0.14.0",
|
"hashbrown 0.14.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "infer"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inflections"
|
name = "inflections"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
|
@ -1302,6 +1308,8 @@ dependencies = [
|
||||||
"glam",
|
"glam",
|
||||||
"gltf",
|
"gltf",
|
||||||
"image",
|
"image",
|
||||||
|
"infer",
|
||||||
|
"mime",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -1373,6 +1381,12 @@ dependencies = [
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
|
@ -1124,6 +1124,12 @@ dependencies = [
|
||||||
"hashbrown 0.14.1",
|
"hashbrown 0.14.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "infer"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inflections"
|
name = "inflections"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
|
@ -1341,6 +1347,8 @@ dependencies = [
|
||||||
"glam",
|
"glam",
|
||||||
"gltf",
|
"gltf",
|
||||||
"image",
|
"image",
|
||||||
|
"infer",
|
||||||
|
"mime",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -1412,6 +1420,12 @@ dependencies = [
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -75,7 +75,8 @@ async fn main() {
|
||||||
|
|
||||||
let mut resman = world.get_resource_mut::<ResourceManager>().unwrap();
|
let mut resman = world.get_resource_mut::<ResourceManager>().unwrap();
|
||||||
let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
|
let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
|
||||||
let cube_model = resman.request::<Model>("assets/cube-embedded.gltf").unwrap();
|
//let cube_model = resman.request::<Model>("assets/cube-embedded.gltf").unwrap();
|
||||||
|
let cube_model = resman.request::<Model>("assets/cube-texture-embedded.gltf").unwrap();
|
||||||
drop(resman);
|
drop(resman);
|
||||||
|
|
||||||
/* world.spawn((MeshComponent::new(
|
/* world.spawn((MeshComponent::new(
|
||||||
|
@ -132,18 +133,18 @@ async fn main() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let jiggle_system = |world: &mut World| -> anyhow::Result<()> {
|
let jiggle_system = |world: &mut World| -> anyhow::Result<()> {
|
||||||
let keys = world.get_resource();
|
let keys = world.get_resource::<InputButtons<KeyCode>>();
|
||||||
if keys.is_none() {
|
if keys.is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
let keys = keys.unwrap();
|
||||||
|
|
||||||
let keys: Ref<InputButtons<KeyCode>> = keys.unwrap();
|
let speed = 0.01;
|
||||||
|
|
||||||
let speed = 0.001;
|
|
||||||
let rot_speed = 1.0;
|
let rot_speed = 1.0;
|
||||||
|
|
||||||
let mut dir_x = 0.0;
|
let mut dir_x = 0.0;
|
||||||
let mut dir_y = 0.0;
|
let mut dir_y = 0.0;
|
||||||
|
let mut dir_z = 0.0;
|
||||||
|
|
||||||
let mut rot_x = 0.0;
|
let mut rot_x = 0.0;
|
||||||
let mut rot_y = 0.0;
|
let mut rot_y = 0.0;
|
||||||
|
@ -164,6 +165,14 @@ async fn main() {
|
||||||
dir_y -= speed;
|
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) {
|
if keys.is_pressed(KeyCode::Left) {
|
||||||
rot_y -= rot_speed;
|
rot_y -= rot_speed;
|
||||||
}
|
}
|
||||||
|
@ -182,7 +191,7 @@ async fn main() {
|
||||||
|
|
||||||
drop(keys);
|
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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,6 +206,7 @@ async fn main() {
|
||||||
t.translation.x *= -1.0; */
|
t.translation.x *= -1.0; */
|
||||||
t.translation.x += dir_x;
|
t.translation.x += dir_x;
|
||||||
t.translation.y += dir_y;
|
t.translation.y += dir_y;
|
||||||
|
t.translation.z += dir_z;
|
||||||
t.rotate_x(math::Angle::Degrees(rot_x));
|
t.rotate_x(math::Angle::Degrees(rot_x));
|
||||||
t.rotate_y(math::Angle::Degrees(rot_y));
|
t.rotate_y(math::Angle::Degrees(rot_y));
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,9 @@ edict = "0.5.0"
|
||||||
glam = "0.24.1"
|
glam = "0.24.1"
|
||||||
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness"] }
|
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness"] }
|
||||||
image = "0.24.7"
|
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"
|
percent-encoding = "2.3.0"
|
||||||
thiserror = "1.0.48"
|
thiserror = "1.0.48"
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
|
|
|
@ -14,4 +14,6 @@ pub mod model;
|
||||||
pub use model::*;
|
pub use model::*;
|
||||||
|
|
||||||
pub mod material;
|
pub mod material;
|
||||||
pub use material::*;
|
pub use material::*;
|
||||||
|
|
||||||
|
pub(crate) mod util;
|
|
@ -2,7 +2,7 @@ use std::{fs::File, sync::Arc, io::Read};
|
||||||
|
|
||||||
use image::ImageError;
|
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};
|
use super::{LoaderError, ResourceLoader};
|
||||||
|
|
||||||
|
@ -14,9 +14,9 @@ impl From<ImageError> for LoaderError {
|
||||||
|
|
||||||
/// A struct that implements the `ResourceLoader` trait used for loading textures.
|
/// A struct that implements the `ResourceLoader` trait used for loading textures.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct TextureLoader;
|
pub struct ImageLoader;
|
||||||
|
|
||||||
impl ResourceLoader for TextureLoader {
|
impl ResourceLoader for ImageLoader {
|
||||||
fn extensions(&self) -> &[&str] {
|
fn extensions(&self) -> &[&str] {
|
||||||
&[
|
&[
|
||||||
// the extensions of these are the names of the formats
|
// the extensions of these are the names of the formats
|
||||||
|
@ -26,11 +26,23 @@ impl ResourceLoader for TextureLoader {
|
||||||
"ff",
|
"ff",
|
||||||
|
|
||||||
// pnm
|
// pnm
|
||||||
"pnm", "pbm", "pgm", "ppm", "pam",
|
"pnm", "pbm", "pgm", "ppm",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
|
fn mime_types(&self) -> &[&str] {
|
||||||
|
&[
|
||||||
|
"image/bmp", "image/vnd.ms-dds", "image/gif", "image/x-icon", "image/jpeg",
|
||||||
|
"image/png", "image/qoi", "image/tga", "image/tiff", "image/webp",
|
||||||
|
|
||||||
|
// no known mime for farbfeld
|
||||||
|
|
||||||
|
// pnm, pbm, pgm, ppm
|
||||||
|
"image/x-portable-anymap", "image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&self, _resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
|
||||||
// check if the file is supported by this loader
|
// check if the file is supported by this loader
|
||||||
if !self.does_support_file(path) {
|
if !self.does_support_file(path) {
|
||||||
return Err(LoaderError::UnsupportedExtension(path.to_string()));
|
return Err(LoaderError::UnsupportedExtension(path.to_string()));
|
||||||
|
@ -54,6 +66,20 @@ impl ResourceLoader for TextureLoader {
|
||||||
|
|
||||||
Ok(Arc::new(res))
|
Ok(Arc::new(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_bytes(&self, _resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
|
||||||
|
let image = image::load_from_memory(&bytes[offset..(length-offset)])
|
||||||
|
.map_err(|e| match e {
|
||||||
|
ImageError::IoError(e) => LoaderError::IoError(e),
|
||||||
|
_ => LoaderError::DecodingError(e.into()),
|
||||||
|
})?;
|
||||||
|
let texture = Texture {
|
||||||
|
image,
|
||||||
|
};
|
||||||
|
let res = Resource::with_data(&uuid::Uuid::new_v4().to_string(), texture);
|
||||||
|
|
||||||
|
Ok(Arc::new(res))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -68,20 +94,22 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_unsupport() {
|
fn check_unsupport() {
|
||||||
let loader = TextureLoader::default();
|
let loader = ImageLoader::default();
|
||||||
assert_eq!(loader.does_support_file("test.gltf"), false);
|
assert_eq!(loader.does_support_file("test.gltf"), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests loading an image
|
/// Tests loading an image
|
||||||
#[test]
|
#[test]
|
||||||
fn image_load() {
|
fn image_load() {
|
||||||
let loader = TextureLoader::default();
|
let mut manager = ResourceManager::new();
|
||||||
loader.load(&get_image("squiggles.png")).unwrap();
|
let loader = ImageLoader::default();
|
||||||
|
loader.load(&mut manager, &get_image("squiggles.png")).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn image_load_unsupported() {
|
fn image_load_unsupported() {
|
||||||
let loader = TextureLoader::default();
|
let mut manager = ResourceManager::new();
|
||||||
assert!(loader.load(&get_image("squiggles.gltf")).is_err());
|
let loader = ImageLoader::default();
|
||||||
|
assert!(loader.load(&mut manager, &get_image("squiggles.gltf")).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
pub mod texture;
|
pub mod image;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
|
||||||
use std::{io, sync::Arc, path::Path, ffi::OsStr};
|
use std::{io, sync::Arc, path::Path, ffi::OsStr};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::resource_manager::ResourceStorage;
|
use crate::{resource_manager::ResourceStorage, ResourceManager};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum LoaderError {
|
pub enum LoaderError {
|
||||||
|
@ -30,8 +30,12 @@ impl From<io::Error> for LoaderError {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ResourceLoader: Send + Sync {
|
pub trait ResourceLoader: Send + Sync {
|
||||||
|
/// Returns the extensions that this loader supports.
|
||||||
fn extensions(&self) -> &[&str];
|
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 {
|
fn does_support_file(&self, path: &str) -> bool {
|
||||||
match Path::new(path).extension().and_then(OsStr::to_str) {
|
match Path::new(path).extension().and_then(OsStr::to_str) {
|
||||||
Some(ext) => {
|
Some(ext) => {
|
||||||
|
@ -41,18 +45,26 @@ pub trait ResourceLoader: Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
|
/// Returns true if this loader supports the mime type.
|
||||||
|
fn does_support_mime(&self, mime: &str) -> bool {
|
||||||
|
self.mime_types().contains(&mime)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a resource from a path.
|
||||||
|
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
|
||||||
|
/// Load a resource from bytes.
|
||||||
|
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{*, texture::TextureLoader};
|
use super::{*, image::ImageLoader};
|
||||||
|
|
||||||
/// Ensure that `does_support_file` works
|
/// Ensure that `does_support_file` works
|
||||||
#[test]
|
#[test]
|
||||||
fn check_support() {
|
fn check_support() {
|
||||||
let loader = TextureLoader::default();
|
let loader = ImageLoader::default();
|
||||||
let extensions = loader.extensions();
|
let extensions = loader.extensions();
|
||||||
let fake_paths: Vec<String> = extensions.iter().map(|e| format!("a.{}", e)).collect();
|
let fake_paths: Vec<String> = extensions.iter().map(|e| format!("a.{}", e)).collect();
|
||||||
for path in fake_paths.iter() {
|
for path in fake_paths.iter() {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use base64::Engine;
|
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;
|
use tracing::debug;
|
||||||
|
|
||||||
|
@ -12,11 +13,25 @@ impl From<gltf::Error> for LoaderError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
enum ModelLoaderError {
|
||||||
|
#[error("The model ({0}) is missing the BIN section in the gltf file")]
|
||||||
|
MissingBin(String),
|
||||||
|
#[error("There was an error with decoding a uri defined in the model: '{0}'")]
|
||||||
|
UriDecodingError(util::UriReadError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModelLoaderError> for LoaderError {
|
||||||
|
fn from(value: ModelLoaderError) -> Self {
|
||||||
|
LoaderError::DecodingError(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ModelLoader;
|
pub struct ModelLoader;
|
||||||
|
|
||||||
impl ModelLoader {
|
impl ModelLoader {
|
||||||
fn parse_uri(uri: &str) -> Option<Vec<u8>> {
|
/* fn parse_uri(containing_path: &str, uri: &str) -> Option<Vec<u8>> {
|
||||||
let uri = uri.strip_prefix("data")?;
|
let uri = uri.strip_prefix("data")?;
|
||||||
let (mime, data) = uri.split_once(",")?;
|
let (mime, data) = uri.split_once(",")?;
|
||||||
|
|
||||||
|
@ -26,11 +41,13 @@ impl ModelLoader {
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_base64 {
|
if is_base64 {
|
||||||
base64::engine::general_purpose::STANDARD.decode(data).ok()
|
Some(base64::engine::general_purpose::STANDARD.decode(data).unwrap())
|
||||||
} else {
|
} else {
|
||||||
Some(data.as_bytes().to_vec())
|
let full_path = format!("{containing_path}/{data}");
|
||||||
|
let buf = std::fs::read(&full_path).unwrap();
|
||||||
|
Some(buf)
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
fn process_node(&self, buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> {
|
fn process_node(&self, buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> {
|
||||||
let mut meshes = vec![];
|
let mut meshes = vec![];
|
||||||
|
@ -61,7 +78,7 @@ impl ModelLoader {
|
||||||
|
|
||||||
// read tex coords
|
// read tex coords
|
||||||
if let Some(tex_coords) = reader.read_tex_coords(0) {
|
if let Some(tex_coords) = reader.read_tex_coords(0) {
|
||||||
let tex_coords: Vec<glam::Vec2> = tex_coords.into_f32().map(|t| t.into()).collect(); // TODO: u16, u8
|
let tex_coords: Vec<glam::Vec2> = tex_coords.into_f32().map(|t| t.into()).collect();
|
||||||
new_mesh.add_attribute(MeshVertexAttribute::TexCoords, VertexAttributeData::Vec2(tex_coords));
|
new_mesh.add_attribute(MeshVertexAttribute::TexCoords, VertexAttributeData::Vec2(tex_coords));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,17 +118,27 @@ impl ResourceLoader for ModelLoader {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(&self, path: &str) -> Result<std::sync::Arc<dyn crate::ResourceStorage>, crate::LoaderError> {
|
fn mime_types(&self) -> &[&str] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<std::sync::Arc<dyn crate::ResourceStorage>, crate::LoaderError> {
|
||||||
// check if the file is supported by this loader
|
// check if the file is supported by this loader
|
||||||
if !self.does_support_file(path) {
|
if !self.does_support_file(path) {
|
||||||
return Err(LoaderError::UnsupportedExtension(path.to_string()));
|
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 gltf = gltf::Gltf::open(path)?;
|
||||||
|
|
||||||
let buffers: Vec<Vec<u8>> = gltf.buffers().map(|b| match b.source() {
|
let buffers: Vec<Vec<u8>> = gltf.buffers().map(|b| match b.source() {
|
||||||
gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec()),
|
gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec())
|
||||||
gltf::buffer::Source::Uri(uri) => ModelLoader::parse_uri(uri),
|
.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();
|
}).flatten().collect();
|
||||||
|
|
||||||
// TODO: Read in multiple scenes
|
// TODO: Read in multiple scenes
|
||||||
|
@ -119,7 +146,7 @@ impl ResourceLoader for ModelLoader {
|
||||||
|
|
||||||
// Load the materials
|
// Load the materials
|
||||||
let materials: Vec<Material> = gltf.materials()
|
let materials: Vec<Material> = gltf.materials()
|
||||||
.map(|mat| Material::from(mat)).collect();
|
.map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect();
|
||||||
|
|
||||||
let meshes: Vec<Mesh> = scene.nodes()
|
let meshes: Vec<Mesh> = scene.nodes()
|
||||||
.map(|node| self.process_node(&buffers, &materials, node))
|
.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))))
|
Ok(Arc::new(Resource::with_data(path, Model::new(meshes))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn crate::ResourceStorage>, LoaderError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -142,10 +173,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_loading() {
|
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 loader = ModelLoader::default();
|
||||||
let model = loader.load(&path).unwrap();
|
let model = loader.load(&mut manager, &path).unwrap();
|
||||||
let model = Arc::downcast::<Resource<Model>>(model.as_arc_any()).unwrap();
|
let model = Arc::downcast::<Resource<Model>>(model.as_arc_any()).unwrap();
|
||||||
let model = model.data.as_ref().unwrap();
|
let model = model.data.as_ref().unwrap();
|
||||||
assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh
|
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.normals().unwrap().len() > 0);
|
||||||
assert!(mesh.tex_coords().unwrap().len() > 0);
|
assert!(mesh.tex_coords().unwrap().len() > 0);
|
||||||
assert!(mesh.indices.clone().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
|
let _mesh_mat = mesh.material(); // inner panic if material was not loaded
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
/// PBR metallic roughness
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -27,10 +29,10 @@ impl From<gltf::material::PbrMetallicRoughness<'_>> for PbrRoughness {
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct PbrGlossiness {
|
pub struct PbrGlossiness {
|
||||||
/// The rgba diffuse color of the material
|
/// The rgba diffuse color of the material
|
||||||
pub diffuse_color: [f32; 4],
|
pub diffuse_color: glam::Vec4,
|
||||||
// The base color texture
|
// The base color texture
|
||||||
// pub diffuse_texture // TODO
|
// pub diffuse_texture // TODO
|
||||||
pub specular: [f32; 3],
|
pub specular: glam::Vec3,
|
||||||
/// The glossiness factor of the material.
|
/// The glossiness factor of the material.
|
||||||
/// From 0.0 (no glossiness) to 1.0 (full glossiness)
|
/// From 0.0 (no glossiness) to 1.0 (full glossiness)
|
||||||
pub glossiness: f32,
|
pub glossiness: f32,
|
||||||
|
@ -40,39 +42,201 @@ pub struct PbrGlossiness {
|
||||||
impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness {
|
impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness {
|
||||||
fn from(value: gltf::material::PbrSpecularGlossiness) -> Self {
|
fn from(value: gltf::material::PbrSpecularGlossiness) -> Self {
|
||||||
PbrGlossiness {
|
PbrGlossiness {
|
||||||
diffuse_color: value.diffuse_factor(),
|
diffuse_color: value.diffuse_factor().into(),
|
||||||
specular: value.specular_factor(),
|
specular: value.specular_factor().into(),
|
||||||
glossiness: value.glossiness_factor()
|
glossiness: value.glossiness_factor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The alpha rendering mode of a material.
|
||||||
|
/// This is essentially a re-export of gltf::material::AlphaMode
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
|
||||||
|
pub enum AlphaMode {
|
||||||
|
/// The alpha value is ignored and the rendered output is fully opaque.
|
||||||
|
#[default]
|
||||||
|
Opaque = 1,
|
||||||
|
|
||||||
|
/// The rendered output is either fully opaque or fully transparent depending on
|
||||||
|
/// the alpha value and the specified alpha cutoff value.
|
||||||
|
Mask,
|
||||||
|
|
||||||
|
/// The alpha value is used, to determine the transparency of the rendered output.
|
||||||
|
/// The alpha cutoff value is ignored.
|
||||||
|
Blend,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<gltf::material::AlphaMode> for AlphaMode {
|
||||||
|
fn from(value: gltf::material::AlphaMode) -> Self {
|
||||||
|
match value {
|
||||||
|
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
|
||||||
|
gltf::material::AlphaMode::Mask => AlphaMode::Mask,
|
||||||
|
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Material {
|
pub struct Material {
|
||||||
pub shader_uuid: Option<u64>,
|
pub shader_uuid: Option<u64>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub double_sided: bool,
|
pub double_sided: bool,
|
||||||
pub pbr_roughness: PbrRoughness,
|
|
||||||
pub pbr_glossiness: Option<PbrGlossiness>,
|
//pub pbr_roughness: PbrRoughness,
|
||||||
pub alpha_cutoff: Option<f32>,
|
/// The RGBA base color of the model. If a texture is supplied with `base_color_texture`, this value
|
||||||
pub alpha_mode: gltf::material::AlphaMode,
|
/// will tint the texture. If a texture is not provided, this value would be the color of the Material.
|
||||||
|
pub base_color: glam::Vec4,
|
||||||
|
/// The metalness of the material
|
||||||
|
/// From 0.0 (non-metal) to 1.0 (metal)
|
||||||
|
pub metallic: f32,
|
||||||
|
/// The roughness of the material
|
||||||
|
/// From 0.0 (smooth) to 1.0 (rough)
|
||||||
|
pub roughness: f32,
|
||||||
|
/// The base color texture of the model.
|
||||||
|
pub base_color_texture: Option<ResHandle<Texture>>,
|
||||||
|
|
||||||
pub texture: Option<ResHandle<Texture>>,
|
/// The metallic-roughness texture.
|
||||||
|
///
|
||||||
|
/// The metalness values are sampled from the B channel. The roughness values are sampled from
|
||||||
|
/// the G channel. These values are linear. If other channels are present (R or A), they are
|
||||||
|
/// ignored for metallic-roughness calculations.
|
||||||
|
pub metallic_roughness_texture: Option<ResHandle<Texture>>,
|
||||||
|
|
||||||
|
/// A set of parameter values that are used to define the specular-glossiness material model
|
||||||
|
/// from Physically-Based Rendering (PBR) methodology.
|
||||||
|
/// GLTF extension: [KHR_materials_pbrSpecularGlossiness](https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness)
|
||||||
|
pub pbr_glossiness: Option<PbrGlossiness>,
|
||||||
|
|
||||||
|
/// The optional alpha cutoff value of the material.
|
||||||
|
pub alpha_cutoff: Option<f32>,
|
||||||
|
|
||||||
|
/// The alpha rendering mode of the material. The material's alpha rendering
|
||||||
|
/// mode enumeration specifying the interpretation of the alpha value of the main
|
||||||
|
/// factor and texture.
|
||||||
|
///
|
||||||
|
/// * In `Opaque` mode (default) the alpha value is ignored
|
||||||
|
/// and the rendered output is fully opaque.
|
||||||
|
/// * In `Mask` mode, the rendered
|
||||||
|
/// output is either fully opaque or fully transparent depending on the alpha
|
||||||
|
/// value and the specified alpha cutoff value.
|
||||||
|
/// * In `Blend` mode, the alpha value is used to composite the source and
|
||||||
|
/// destination areas and the rendered output is combined with the background
|
||||||
|
/// using the normal painting operation (i.e. the Porter and Duff over
|
||||||
|
/// operator).
|
||||||
|
pub alpha_mode: AlphaMode,
|
||||||
|
|
||||||
|
//pub texture: Option<ResHandle<Texture>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<gltf::Material<'_>> for Material {
|
impl Material {
|
||||||
fn from(value: gltf::Material) -> Self {
|
/// Get a uri's identifier
|
||||||
|
///
|
||||||
|
/// I'm not actually sure how identifiable this would be
|
||||||
|
fn uri_ident(gltf_rel_path: &str, uri: &str) -> String {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
uri.hash(&mut hasher);
|
||||||
|
let hash = hasher.finish();
|
||||||
|
|
||||||
|
format!("{gltf_rel_path};{hash}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source_ident(gltf_rel_path: &str, src: &gltf::image::Source) -> Option<String> {
|
||||||
|
match src {
|
||||||
|
gltf::image::Source::View { view, mime_type } => {
|
||||||
|
let buf = view.buffer();
|
||||||
|
let src = buf.source();
|
||||||
|
|
||||||
|
match src {
|
||||||
|
gltf::buffer::Source::Bin => None,
|
||||||
|
gltf::buffer::Source::Uri(uri) => {
|
||||||
|
Some(Material::uri_ident(gltf_rel_path, uri))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gltf::image::Source::Uri { uri, mime_type } => {
|
||||||
|
Some(Material::uri_ident(gltf_rel_path, uri))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_source(resource_manager: &mut ResourceManager, gltf_rel_path: &str, src: gltf::image::Source) -> Result<Vec<u8>, util::UriReadError> {
|
||||||
|
// TODO: Don't copy sources
|
||||||
|
match src {
|
||||||
|
gltf::image::Source::View { view, mime_type } => {
|
||||||
|
let buf = view.buffer();
|
||||||
|
let src = buf.source();
|
||||||
|
|
||||||
|
let offset = view.offset();
|
||||||
|
let len = view.length();
|
||||||
|
|
||||||
|
match src {
|
||||||
|
gltf::buffer::Source::Bin => todo!("Read material source from gltf Bin"),
|
||||||
|
gltf::buffer::Source::Uri(uri) => {
|
||||||
|
util::gltf_read_buffer_uri(gltf_rel_path, uri)
|
||||||
|
.map(|mut buf| {
|
||||||
|
buf.drain(0..offset);
|
||||||
|
buf.truncate(len);
|
||||||
|
buf
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gltf::image::Source::Uri { uri, mime_type } => {
|
||||||
|
util::gltf_read_buffer_uri(gltf_rel_path, uri)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_texture(resource_manager: &mut ResourceManager, gltf_rel_path: &str, texture_info: gltf::texture::Info<'_>) -> ResHandle<Texture> {
|
||||||
|
// TODO: texture_info.tex_coord()
|
||||||
|
let tex = texture_info.texture();
|
||||||
|
let img = tex.source();
|
||||||
|
let src = img.source();
|
||||||
|
|
||||||
|
let buf = Material::read_source(resource_manager, gltf_rel_path, src).unwrap();
|
||||||
|
let buflen = buf.len();
|
||||||
|
let mime_type = infer::get(&buf).expect("Failed to get file type").mime_type();
|
||||||
|
|
||||||
|
resource_manager.load_bytes::<Texture>(&uuid::Uuid::new_v4().to_string(), mime_type,
|
||||||
|
buf, 0, buflen).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the Material from a gltf::Material.
|
||||||
|
///
|
||||||
|
/// `gltf_rel_path`: The relative path of the gltf file,
|
||||||
|
/// e.g. gltf model path is "resource/models/player.gltf", the relative path would be "resource/models/"
|
||||||
|
pub fn from_gltf(resource_manager: &mut ResourceManager, gltf_rel_path: &str, gltf_mat: gltf::Material) -> Self {
|
||||||
|
let pbr_rough = gltf_mat.pbr_metallic_roughness();
|
||||||
|
let base_color = pbr_rough.base_color_factor().into();
|
||||||
|
let metallic = pbr_rough.metallic_factor();
|
||||||
|
let roughness = pbr_rough.roughness_factor();
|
||||||
|
|
||||||
|
let base_color_texture = pbr_rough.base_color_texture()
|
||||||
|
.map(|info| Material::load_texture(resource_manager, gltf_rel_path, info));
|
||||||
|
|
||||||
|
let metallic_roughness_texture = pbr_rough.metallic_roughness_texture()
|
||||||
|
.map(|info| Material::load_texture(resource_manager, gltf_rel_path, info));
|
||||||
|
|
||||||
|
/* let base_color_texture = if let Some(base_tex_info) = pbr_rough.base_color_texture() {
|
||||||
|
Some(Material::load_texture(resource_manager, gltf_rel_path, base_tex_info))
|
||||||
|
} else { None }; */
|
||||||
|
|
||||||
Material {
|
Material {
|
||||||
name: value.name()
|
name: gltf_mat.name()
|
||||||
.map(|s| s.to_string()),
|
.map(|s| s.to_string()),
|
||||||
double_sided: value.double_sided(),
|
double_sided: gltf_mat.double_sided(),
|
||||||
pbr_roughness: value.pbr_metallic_roughness().into(),
|
base_color,
|
||||||
pbr_glossiness: value.pbr_specular_glossiness()
|
metallic,
|
||||||
|
roughness,
|
||||||
|
pbr_glossiness: gltf_mat.pbr_specular_glossiness()
|
||||||
.map(|o| o.into()),
|
.map(|o| o.into()),
|
||||||
alpha_cutoff: value.alpha_cutoff(),
|
alpha_cutoff: gltf_mat.alpha_cutoff(),
|
||||||
alpha_mode: value.alpha_mode(),
|
alpha_mode: gltf_mat.alpha_mode().into(),
|
||||||
shader_uuid: None,
|
shader_uuid: None,
|
||||||
texture: None,
|
|
||||||
|
// TODO
|
||||||
|
base_color_texture,
|
||||||
|
metallic_roughness_texture,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,6 +10,15 @@ pub enum MeshIndices {
|
||||||
U32(Vec<u32>),
|
U32(Vec<u32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MeshIndices {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
MeshIndices::U16(v) => v.len(),
|
||||||
|
MeshIndices::U32(v) => v.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* impl From<Vec<u8>> for MeshIndices {
|
/* impl From<Vec<u8>> for MeshIndices {
|
||||||
fn from(value: Vec<u8>) -> Self {
|
fn from(value: Vec<u8>) -> Self {
|
||||||
MeshIndices::U8(value)
|
MeshIndices::U8(value)
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{sync::Arc, collections::{HashMap, hash_map::DefaultHasher}, hash::{Has
|
||||||
|
|
||||||
use thiserror::Error;
|
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 {
|
pub trait ResourceStorage: Send + Sync + Any + 'static {
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
@ -31,6 +31,10 @@ pub enum RequestError {
|
||||||
Loader(LoaderError),
|
Loader(LoaderError),
|
||||||
#[error("The file extension is unsupported: '{0}'")]
|
#[error("The file extension is unsupported: '{0}'")]
|
||||||
UnsupportedFileExtension(String),
|
UnsupportedFileExtension(String),
|
||||||
|
#[error("The mimetype is unsupported: '{0}'")]
|
||||||
|
UnsupportedMime(String),
|
||||||
|
#[error("The identifier is not found: '{0}'")]
|
||||||
|
IdentNotFound(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LoaderError> for RequestError {
|
impl From<LoaderError> for RequestError {
|
||||||
|
@ -39,16 +43,19 @@ impl From<LoaderError> for RequestError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A struct that stores all Manager data. This is requried for sending
|
||||||
|
//struct ManagerStorage
|
||||||
|
|
||||||
pub struct ResourceManager {
|
pub struct ResourceManager {
|
||||||
resources: HashMap<String, Arc<dyn ResourceStorage>>,
|
resources: HashMap<String, Arc<dyn ResourceStorage>>,
|
||||||
loaders: Vec<Box<dyn ResourceLoader>>,
|
loaders: Vec<Arc<dyn ResourceLoader>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceManager {
|
impl ResourceManager {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
resources: HashMap::new(),
|
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)) {
|
.find(|l| l.does_support_file(path)) {
|
||||||
|
|
||||||
// Load the resource and store it
|
// 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());
|
self.resources.insert(path.to_string(), res.clone());
|
||||||
|
|
||||||
// convert Arc<dyn ResourceStorage> to Arc<Resource<T>
|
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
|
||||||
let res = res.as_arc_any();
|
let res = res.as_arc_any();
|
||||||
let res = res.downcast::<Resource<T>>()
|
let res = res.downcast::<Resource<T>>()
|
||||||
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
|
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
|
||||||
|
@ -80,6 +88,49 @@ impl ResourceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store bytes in the manager. If there is already an entry with the same identifier it will be updated.
|
||||||
|
///
|
||||||
|
/// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `ident` - The identifier to store along with these bytes. Make sure its unique to avoid overriding something.
|
||||||
|
/// * `bytes` - The bytes to store.
|
||||||
|
///
|
||||||
|
/// Returns: The `Arc` to the now stored resource
|
||||||
|
pub fn load_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<Resource<T>>, RequestError> {
|
||||||
|
if let Some(loader) = self.loaders.iter()
|
||||||
|
.find(|l| l.does_support_mime(mime_type)) {
|
||||||
|
let loader = loader.clone();
|
||||||
|
let res = loader.load_bytes(self, bytes, offset, length)?;
|
||||||
|
self.resources.insert(ident.to_string(), res.clone());
|
||||||
|
// code here...
|
||||||
|
|
||||||
|
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
|
||||||
|
let res = res.as_arc_any();
|
||||||
|
let res = res.downcast::<Resource<T>>()
|
||||||
|
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
} else {
|
||||||
|
Err(RequestError::UnsupportedMime(mime_type.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Requests bytes from the manager.
|
||||||
|
pub fn request_loaded_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str) -> Result<Arc<Resource<T>>, RequestError> {
|
||||||
|
match self.resources.get(&ident.to_string()) {
|
||||||
|
Some(res) => {
|
||||||
|
let res = res.clone().as_arc_any();
|
||||||
|
let res = res.downcast::<Resource<T>>().expect("Failure to downcast resource");
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(RequestError::IdentNotFound(ident.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
use base64::Engine;
|
||||||
|
use thiserror::Error;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum UriReadError {
|
||||||
|
#[error("IOError: '{0}'")]
|
||||||
|
IoError(io::Error),
|
||||||
|
|
||||||
|
// From is implemented for this field in each loader module
|
||||||
|
#[error("Base64 decoding error: '{0}'")]
|
||||||
|
Base64Decode(base64::DecodeError),
|
||||||
|
|
||||||
|
#[error("Some data was missing from the uri")]
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a buffer's uri string into a byte buffer.
|
||||||
|
///
|
||||||
|
/// * `containing_path`: The path of the containing folder of the buffers "parent",
|
||||||
|
/// the parent being where this buffer is defined in,
|
||||||
|
/// i.e. parent="resources/models/player.gltf", containing="reousrce/models"
|
||||||
|
pub(crate) fn gltf_read_buffer_uri(containing_path: &str, uri: &str) -> Result<Vec<u8>, UriReadError> {
|
||||||
|
let uri = uri.strip_prefix("data").ok_or(UriReadError::None)?;
|
||||||
|
let (mime, data) = uri.split_once(",").ok_or(UriReadError::None)?;
|
||||||
|
|
||||||
|
let (_mime, is_base64) = match mime.strip_suffix(";base64") {
|
||||||
|
Some(mime) => (mime, true),
|
||||||
|
None => (mime, false),
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_base64 {
|
||||||
|
base64::engine::general_purpose::STANDARD.decode(data).map_err(|e| UriReadError::Base64Decode(e))
|
||||||
|
} else {
|
||||||
|
let full_path = format!("{containing_path}/{data}");
|
||||||
|
std::fs::read(&full_path).map_err(|e| UriReadError::IoError(e))
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
@ -0,0 +1,137 @@
|
||||||
|
{
|
||||||
|
"asset":{
|
||||||
|
"generator":"Khronos glTF Blender I/O v3.6.6",
|
||||||
|
"version":"2.0"
|
||||||
|
},
|
||||||
|
"scene":0,
|
||||||
|
"scenes":[
|
||||||
|
{
|
||||||
|
"name":"Scene",
|
||||||
|
"nodes":[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes":[
|
||||||
|
{
|
||||||
|
"mesh":0,
|
||||||
|
"name":"Cube"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials":[
|
||||||
|
{
|
||||||
|
"doubleSided":true,
|
||||||
|
"name":"Material",
|
||||||
|
"pbrMetallicRoughness":{
|
||||||
|
"baseColorTexture":{
|
||||||
|
"index":0
|
||||||
|
},
|
||||||
|
"metallicFactor":0,
|
||||||
|
"roughnessFactor":0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes":[
|
||||||
|
{
|
||||||
|
"name":"Cube",
|
||||||
|
"primitives":[
|
||||||
|
{
|
||||||
|
"attributes":{
|
||||||
|
"POSITION":0,
|
||||||
|
"TEXCOORD_0":1,
|
||||||
|
"NORMAL":2
|
||||||
|
},
|
||||||
|
"indices":3,
|
||||||
|
"material":0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures":[
|
||||||
|
{
|
||||||
|
"sampler":0,
|
||||||
|
"source":0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images":[
|
||||||
|
{
|
||||||
|
"mimeType":"image/png",
|
||||||
|
"name":"uvgrid",
|
||||||
|
"uri":"uvgrid.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors":[
|
||||||
|
{
|
||||||
|
"bufferView":0,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":24,
|
||||||
|
"max":[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"min":[
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":1,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":24,
|
||||||
|
"type":"VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":2,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":24,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":3,
|
||||||
|
"componentType":5123,
|
||||||
|
"count":36,
|
||||||
|
"type":"SCALAR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews":[
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":288,
|
||||||
|
"byteOffset":0,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":192,
|
||||||
|
"byteOffset":288,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":288,
|
||||||
|
"byteOffset":480,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":72,
|
||||||
|
"byteOffset":768,
|
||||||
|
"target":34963
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"samplers":[
|
||||||
|
{
|
||||||
|
"magFilter":9729,
|
||||||
|
"minFilter":9987
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers":[
|
||||||
|
{
|
||||||
|
"byteLength":840,
|
||||||
|
"uri":"texture-sep.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
|
@ -21,6 +21,7 @@ use crate::ecs::components::transform::TransformComponent;
|
||||||
use super::camera::RenderCamera;
|
use super::camera::RenderCamera;
|
||||||
use super::desc_buf_lay::DescVertexBufferLayout;
|
use super::desc_buf_lay::DescVertexBufferLayout;
|
||||||
use super::texture::RenderTexture;
|
use super::texture::RenderTexture;
|
||||||
|
use super::vertex::Vertex;
|
||||||
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
|
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
|
||||||
|
|
||||||
use lyra_resource::Mesh;
|
use lyra_resource::Mesh;
|
||||||
|
@ -443,15 +444,26 @@ impl BasicRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) {
|
fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) {
|
||||||
let vertices = mesh.position().unwrap();
|
let positions = mesh.position().unwrap();
|
||||||
|
let tex_coords: Vec<glam::Vec2> = mesh.tex_coords().cloned()
|
||||||
|
.unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]);
|
||||||
|
|
||||||
|
debug!("Pos count: {}, tex coords count: {}", positions.len(), tex_coords.len());
|
||||||
|
assert!(positions.len() == tex_coords.len());
|
||||||
|
|
||||||
|
let vertex_inputs: Vec<Vertex> = std::iter::zip(positions, tex_coords.into_iter())
|
||||||
|
.map(|(v, t)| Vertex::new(v.clone(), t))
|
||||||
|
.collect();
|
||||||
|
println!("Vertex inputs: {:?}", vertex_inputs.as_slice());
|
||||||
|
|
||||||
let vertex_buffer = self.device.create_buffer_init(
|
let vertex_buffer = self.device.create_buffer_init(
|
||||||
&wgpu::util::BufferInitDescriptor {
|
&wgpu::util::BufferInitDescriptor {
|
||||||
label: Some("Vertex Buffer"),
|
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,
|
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() {
|
let indices = match mesh.indices.as_ref() {
|
||||||
Some(indices) => {
|
Some(indices) => {
|
||||||
|
@ -483,7 +495,7 @@ impl BasicRenderer {
|
||||||
fn create_mesh_buffers(&mut self, mesh: &Mesh, transform_indices: TransformBufferIndices) -> RenderBufferStorage {
|
fn create_mesh_buffers(&mut self, mesh: &Mesh, transform_indices: TransformBufferIndices) -> RenderBufferStorage {
|
||||||
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh);
|
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 image = &model_texture.data.as_ref().unwrap().image;
|
||||||
let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap();
|
let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
//@location(1) tex_coords: vec2<f32>,
|
@location(1) tex_coords: vec2<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
|
@ -25,7 +25,7 @@ fn vs_main(
|
||||||
model: VertexInput,
|
model: VertexInput,
|
||||||
) -> VertexOutput {
|
) -> VertexOutput {
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.tex_coords = vec2<f32>(1.0, 1.0);
|
out.tex_coords = model.tex_coords;
|
||||||
out.clip_position = camera.view_proj * u_model_transform * vec4<f32>(model.position, 1.0);
|
out.clip_position = camera.view_proj * u_model_transform * vec4<f32>(model.position, 1.0);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,37 @@
|
||||||
use glam::Vec3;
|
|
||||||
|
|
||||||
use super::desc_buf_lay::DescVertexBufferLayout;
|
use super::desc_buf_lay::DescVertexBufferLayout;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct Vertex {
|
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 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 {
|
impl DescVertexBufferLayout for Vertex {
|
||||||
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||||
wgpu::VertexBufferLayout {
|
wgpu::VertexBufferLayout {
|
||||||
array_stride: std::mem::size_of::<Vec3>() as wgpu::BufferAddress,
|
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
||||||
step_mode: wgpu::VertexStepMode::Vertex,
|
step_mode: wgpu::VertexStepMode::Vertex,
|
||||||
attributes: &[
|
attributes: &[
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
shader_location: 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,
|
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||||
shader_location: 1,
|
shader_location: 1,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x2, // Vec2
|
||||||
} */
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue