separate GLTF loader to its own crate

This commit is contained in:
SeanOMik 2024-11-01 12:09:01 -04:00
parent 3ce9ab6fb3
commit 5542467d7e
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
38 changed files with 375 additions and 333 deletions

32
Cargo.lock generated
View File

@ -1829,6 +1829,7 @@ dependencies = [
"itertools 0.13.0",
"lyra-ecs",
"lyra-game-derive",
"lyra-gltf",
"lyra-math",
"lyra-reflect",
"lyra-resource",
@ -1860,6 +1861,34 @@ dependencies = [
"syn 2.0.77",
]
[[package]]
name = "lyra-gltf"
version = "0.0.1"
dependencies = [
"anyhow",
"async-std",
"base64 0.21.7",
"crossbeam",
"glam",
"gltf",
"image",
"infer",
"instant",
"lyra-ecs",
"lyra-math",
"lyra-reflect",
"lyra-resource",
"lyra-scene",
"mime",
"notify",
"notify-debouncer-full",
"percent-encoding",
"rand",
"thiserror",
"tracing",
"uuid",
]
[[package]]
name = "lyra-math"
version = "0.1.0"
@ -1894,14 +1923,12 @@ dependencies = [
"base64 0.21.7",
"crossbeam",
"glam",
"gltf",
"image",
"infer",
"instant",
"lyra-ecs",
"lyra-math",
"lyra-reflect",
"lyra-scene",
"mime",
"notify",
"notify-debouncer-full",
@ -1920,6 +1947,7 @@ dependencies = [
"lyra-ecs",
"lyra-math",
"lyra-reflect",
"lyra-resource",
]
[[package]]

View File

@ -10,6 +10,7 @@ lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
lyra-math = { path = "../lyra-math" }
lyra-scene = { path = "../lyra-scene" }
lyra-gltf = { path = "../lyra-gltf" }
wgsl_preprocessor = { path = "../wgsl-preprocessor" }
winit = "0.30.5"

View File

@ -29,5 +29,6 @@ pub use lyra_resource as assets;
pub use lyra_ecs as ecs;
pub use lyra_math as math;
pub use lyra_reflect as reflect;
pub use lyra_gltf as gltf;
pub use plugin::DefaultPlugins;

View File

@ -1,4 +1,6 @@
use lyra_ecs::query::ResMut;
use lyra_ecs::CommandQueue;
use lyra_gltf::GltfLoader;
use lyra_resource::ResourceManager;
use crate::game::App;
@ -113,6 +115,7 @@ impl Plugin for DefaultPlugins {
CommandQueuePlugin.setup(app);
InputPlugin.setup(app);
ResourceManagerPlugin.setup(app);
GltfPlugin.setup(app);
WindowPlugin::default().setup(app);
DeltaTimePlugin.setup(app);
}
@ -128,3 +131,13 @@ impl Plugin for CommandQueuePlugin {
app.world.add_resource(CommandQueue::default());
}
}
#[derive(Default)]
pub struct GltfPlugin;
impl Plugin for GltfPlugin {
fn setup(&mut self, app: &mut App) {
let man: ResMut<ResourceManager> = app.world.get_resource_or_default();
man.register_loader::<GltfLoader>();
}
}

View File

@ -12,7 +12,8 @@ use lyra_ecs::{
Entity, ResourceObject, World,
};
use lyra_game_derive::RenderGraphLabel;
use lyra_resource::{gltf::Mesh, ResHandle};
use lyra_resource::ResHandle;
use lyra_gltf::Mesh;
use lyra_scene::SceneGraph;
use rustc_hash::FxHashMap;
use tracing::{debug, instrument};
@ -93,10 +94,10 @@ impl MeshPrepNode {
if let Some(index_buffer) = buffers.buffer_indices.as_ref() {
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::gltf::MeshIndices::U16(v) => {
lyra_gltf::MeshIndices::U16(v) => {
bytemuck::pod_align_to::<u16, u32>(v).1
}
lyra_resource::gltf::MeshIndices::U32(v) => {
lyra_gltf::MeshIndices::U32(v) => {
bytemuck::pod_align_to::<u32, u32>(v).1
}
};
@ -137,10 +138,10 @@ impl MeshPrepNode {
let indices = match mesh.indices.as_ref() {
Some(indices) => {
let (idx_type, len, contents) = match indices {
lyra_resource::gltf::MeshIndices::U16(v) => {
lyra_gltf::MeshIndices::U16(v) => {
(wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v))
}
lyra_resource::gltf::MeshIndices::U32(v) => {
lyra_gltf::MeshIndices::U32(v) => {
(wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v))
}
};
@ -518,7 +519,7 @@ impl GpuMaterial {
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &Arc<wgpu::BindGroupLayout>,
mat: &lyra_resource::gltf::Material,
mat: &lyra_gltf::Material,
) -> Self {
//let specular = mat.specular.as_ref().unwrap_or_default();
//let specular_

View File

@ -21,7 +21,7 @@ fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Arc
}
impl MaterialSpecular {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Specular) -> Self {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_gltf::Specular) -> Self {
let tex = texture_to_render(device, queue, &bg_layout, &value.texture);
let color_tex = texture_to_render(device, queue, &bg_layout, &value.color_texture);
@ -46,7 +46,7 @@ pub struct Material {
}
impl Material {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Material) -> Self {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_gltf::Material) -> Self {
let diffuse_texture = texture_to_render(device, queue, &bg_layout, &value.base_color_texture);
let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s));

View File

@ -0,0 +1,34 @@
[package]
name = "lyra-gltf"
version = "0.0.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
lyra-math = { path = "../lyra-math" }
lyra-scene = { path = "../lyra-scene" }
lyra-resource = { path = "../lyra-resource" }
anyhow = "1.0.75"
base64 = "0.21.4"
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
glam = "0.29.0"
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] }
image = "0.25.2"
# not using custom matcher, or file type from file path
infer = { version = "0.15.0", default-features = false }
mime = "0.3.17"
notify = "6.1.1"
notify-debouncer-full = "0.3.1"
#notify = { version = "6.1.1", default-features = false, features = [ "fsevent-sys", "macos_fsevent" ]} # disables crossbeam-channel
percent-encoding = "2.3.0"
thiserror = "1.0.48"
tracing = "0.1.37"
uuid = { version = "1.4.1", features = ["v4"] }
instant = "0.1"
async-std = "1.12.0"
[dev-dependencies]
rand = "0.8.5"

104
crates/lyra-gltf/src/lib.rs Normal file
View File

@ -0,0 +1,104 @@
pub mod loader;
use base64::Engine;
pub use loader::*;
pub mod material;
use lyra_reflect::Reflect;
use lyra_resource::{ResHandle, ResourceData, UntypedResHandle};
use lyra_scene::SceneGraph;
pub use material::*;
pub mod mesh;
pub use mesh::*;
pub mod scene;
pub use scene::*;
#[allow(unused_imports)]
pub(crate) mod lyra_engine {
pub(crate) mod ecs {
pub use lyra_ecs::*;
}
pub(crate) mod reflect {
pub use lyra_reflect::*;
}
}
use thiserror::Error;
use std::io;
#[allow(dead_code)]
#[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="resource/models"
pub(crate) fn gltf_read_buffer_uri(containing_path: &str, uri: &str) -> Result<Vec<u8>, UriReadError> {
if let Some((mime, data)) = uri.strip_prefix("data")
.and_then(|uri| 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)
.map_err(UriReadError::Base64Decode)
} else {
Ok(data.as_bytes().to_vec())
}
} else {
let full_path = format!("{containing_path}/{uri}");
std::fs::read(full_path).map_err(UriReadError::IoError)
}
}
/// A loaded Gltf file
#[derive(Clone, Default, Reflect)]
pub struct Gltf {
pub scenes: Vec<ResHandle<SceneGraph>>,
pub materials: Vec<ResHandle<Material>>,
pub meshes: Vec<ResHandle<Mesh>>,
}
impl ResourceData for Gltf {
fn dependencies(&self) -> Vec<UntypedResHandle> {
let mut deps = vec![];
for scene in self.scenes.iter() {
deps.push(scene.untyped_clone())
}
for mat in self.materials.iter() {
deps.push(mat.untyped_clone())
}
for mesh in self.meshes.iter() {
deps.push(mesh.untyped_clone())
}
deps
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

@ -7,23 +7,19 @@ use lyra_math::Transform;
use lyra_scene::{SceneGraph, SceneNode, WorldTransform};
use thiserror::Error;
use crate::{loader::{LoaderError, PinedBoxLoaderFuture, ResourceLoader}, util, ResHandle, ResourceData, ResourceManager, ResourceStorage};
use lyra_resource::{loader::{LoaderError, PinedBoxLoaderFuture, ResourceLoader}, ResHandle, ResourceData, ResourceManager, ResourceStorage};
use crate::{gltf_read_buffer_uri, UriReadError};
use super::{Gltf, GltfNode, Material, Mesh, MeshIndices, MeshVertexAttribute, VertexAttributeData};
use tracing::debug;
impl From<gltf::Error> for LoaderError {
fn from(value: gltf::Error) -> Self {
LoaderError::DecodingError(value.into())
}
}
#[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),
UriDecodingError(UriReadError),
}
impl From<ModelLoaderError> for LoaderError {
@ -45,9 +41,9 @@ pub(crate) struct GltfLoadContext<'a> {
}
#[derive(Default)]
pub struct ModelLoader;
pub struct GltfLoader;
impl ModelLoader {
impl GltfLoader {
/* fn parse_uri(containing_path: &str, uri: &str) -> Option<Vec<u8>> {
let uri = uri.strip_prefix("data")?;
let (mime, data) = uri.split_once(",")?;
@ -136,7 +132,7 @@ impl ModelLoader {
}
for child in gnode.children() {
let cmesh = ModelLoader::process_node(ctx, materials, scene, &scene_node, child);
let cmesh = GltfLoader::process_node(ctx, materials, scene, &scene_node, child);
node.children.push(cmesh);
}
@ -159,7 +155,7 @@ impl ModelLoader {
}
}
impl ResourceLoader for ModelLoader {
impl ResourceLoader for GltfLoader {
fn extensions(&self) -> &[&str] {
&[
"gltf", "glb"
@ -184,7 +180,8 @@ impl ResourceLoader for ModelLoader {
parent_path.pop();
let parent_path = parent_path.display().to_string();
let gltf = gltf::Gltf::open(&path)?;
let gltf = gltf::Gltf::open(&path)
.map_err(|ge| LoaderError::DecodingError(ge.into()))?;
let mut use_bin = false;
let buffers: Vec<Vec<u8>> = gltf.buffers().flat_map(|b| match b.source() {
@ -193,7 +190,7 @@ impl ResourceLoader for ModelLoader {
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)
gltf::buffer::Source::Uri(uri) => gltf_read_buffer_uri(&parent_path, uri)
.map_err(ModelLoaderError::UriDecodingError),
}).collect();
@ -219,7 +216,7 @@ impl ResourceLoader for ModelLoader {
let root_node = graph.root_node();
for node in scene.nodes() {
let n = ModelLoader::process_node(&mut context, &materials, &mut graph, &root_node, node);
let n = GltfLoader::process_node(&mut context, &materials, &mut graph, &root_node, node);
if let Some(mesh) = n.mesh {
gltf_out.meshes.push(mesh.clone());
@ -272,10 +269,12 @@ impl ResourceLoader for ModelLoader {
#[cfg(test)]
mod tests {
use std::time::Duration;
use lyra_ecs::{query::Entities, relation::ChildOf};
use lyra_scene::WorldTransform;
use crate::tests::busy_wait_resource;
//use lyra_resource::tests::busy_wait_resource;
use super::*;
@ -291,7 +290,7 @@ mod tests {
let manager = ResourceManager::new();
let gltf = manager.request::<Gltf>(&path).unwrap();
busy_wait_resource(&gltf, 15.0);
assert!(gltf.wait_for_load_timeout(Duration::from_secs(10)), "failed to load gltf, hit 10 second timeout");
let gltf = gltf.data_ref().unwrap();
assert_eq!(gltf.scenes.len(), 1);

View File

@ -2,9 +2,9 @@ use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}};
use gltf::texture::{MagFilter, MinFilter};
use lyra_reflect::Reflect;
use crate::{lyra_engine, optionally_add_to_dep, ResourceData};
use lyra_resource::{optionally_add_to_dep, FilterMode, Image, ResHandle, ResourceData, Texture, TextureSampler, WrappingMode};
use crate::{gltf_read_buffer_uri, lyra_engine, UriReadError};
use crate::{util, FilterMode, Image, ResHandle, Texture, TextureSampler, WrappingMode};
use super::loader::GltfLoadContext;
/// PBR metallic roughness
@ -184,8 +184,6 @@ pub struct Material {
}
impl ResourceData for Material {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
let mut deps = vec![];
@ -242,7 +240,7 @@ impl Material {
}
}
fn read_source(context: &mut GltfLoadContext, src: gltf::image::Source) -> Result<Vec<u8>, util::UriReadError> {
fn read_source(context: &mut GltfLoadContext, src: gltf::image::Source) -> Result<Vec<u8>, UriReadError> {
let gltf_rel_path = context.gltf_parent_path;
// TODO: Don't copy sources
match src {
@ -261,7 +259,7 @@ impl Material {
Ok(b)
},
gltf::buffer::Source::Uri(uri) => {
util::gltf_read_buffer_uri(gltf_rel_path, uri)
gltf_read_buffer_uri(gltf_rel_path, uri)
.map(|mut buf| {
buf.drain(0..offset);
buf.truncate(len);
@ -271,7 +269,7 @@ impl Material {
}
},
gltf::image::Source::Uri { uri, mime_type: _ } => {
util::gltf_read_buffer_uri(gltf_rel_path, uri)
gltf_read_buffer_uri(gltf_rel_path, uri)
},
}
}
@ -292,11 +290,11 @@ impl Material {
let samp = tex.sampler();
let samp = TextureSampler {
mag_filter: samp.mag_filter().map(FilterMode::from_mag_filter),
min_filter: samp.min_filter().map(FilterMode::from_min_filter),
mipmap_filter: samp.min_filter().and_then(FilterMode::mipmap_filter),
wrap_u: samp.wrap_s().into(),
wrap_v: samp.wrap_t().into(),
mag_filter: samp.mag_filter().map(filter_mode_from_mag_filter),
min_filter: samp.min_filter().map(filter_mode_from_min_filter),
mipmap_filter: samp.min_filter().and_then(filter_mode_mipmap_filter),
wrap_u: wrapping_mode_from_gltf_wrapping_mode(samp.wrap_s()),
wrap_v: wrapping_mode_from_gltf_wrapping_mode(samp.wrap_t()),
wrap_w: WrappingMode::ClampToEdge,
};
@ -347,20 +345,9 @@ impl Material {
}
}
impl From<gltf::texture::MagFilter> for FilterMode {
#[inline(always)]
fn from(value: gltf::texture::MagFilter) -> Self {
match value {
gltf::texture::MagFilter::Nearest => Self::Nearest,
gltf::texture::MagFilter::Linear => Self::Linear,
}
}
}
impl FilterMode {
/// Get the MinFilter mode and the mipmap filter mode from gltf MinFilter
#[inline(always)]
pub fn from_min_filter(minf: MinFilter) -> FilterMode {
/// Get the MinFilter mode and the mipmap filter mode from gltf MinFilter
#[inline(always)]
fn filter_mode_from_min_filter(minf: MinFilter) -> FilterMode {
match minf {
MinFilter::Nearest => FilterMode::Nearest,
MinFilter::Linear => FilterMode::Linear,
@ -369,18 +356,18 @@ impl FilterMode {
MinFilter::NearestMipmapLinear => FilterMode::Nearest,
MinFilter::LinearMipmapLinear => FilterMode::Linear,
}
}
}
#[inline(always)]
pub fn from_mag_filter(magf: MagFilter) -> FilterMode {
#[inline(always)]
fn filter_mode_from_mag_filter(magf: MagFilter) -> FilterMode {
match magf {
MagFilter::Nearest => FilterMode::Nearest,
MagFilter::Linear => FilterMode::Linear,
}
}
}
#[inline(always)]
pub fn mipmap_filter(minf: MinFilter) -> Option<FilterMode> {
#[inline(always)]
fn filter_mode_mipmap_filter(minf: MinFilter) -> Option<FilterMode> {
match minf {
MinFilter::Nearest => None,
MinFilter::Linear => None,
@ -389,16 +376,13 @@ impl FilterMode {
MinFilter::NearestMipmapLinear => Some(FilterMode::Linear),
MinFilter::LinearMipmapLinear => Some(FilterMode::Linear),
}
}
}
impl From<gltf::texture::WrappingMode> for WrappingMode {
#[inline(always)]
fn from(value: gltf::texture::WrappingMode) -> Self {
#[inline(always)]
fn wrapping_mode_from_gltf_wrapping_mode(value: gltf::texture::WrappingMode) -> WrappingMode {
match value {
gltf::texture::WrappingMode::ClampToEdge => Self::ClampToEdge,
gltf::texture::WrappingMode::MirroredRepeat => Self::MirroredRepeat,
gltf::texture::WrappingMode::Repeat => Self::Repeat,
}
gltf::texture::WrappingMode::ClampToEdge => WrappingMode::ClampToEdge,
gltf::texture::WrappingMode::MirroredRepeat => WrappingMode::MirroredRepeat,
gltf::texture::WrappingMode::Repeat => WrappingMode::Repeat,
}
}

View File

@ -0,0 +1,34 @@
use lyra_math::Transform;
use lyra_resource::{optionally_add_to_dep, ResourceData, UntypedResHandle};
use super::Mesh;
use crate::ResHandle;
/// A Node in the Gltf file
#[derive(Clone, Default)]
pub struct GltfNode {
pub name: Option<String>,
pub mesh: Option<ResHandle<Mesh>>,
pub transform: Transform,
pub children: Vec<GltfNode>,
}
impl ResourceData for GltfNode {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
let mut deps: Vec<UntypedResHandle> = self.children.iter()
.flat_map(|c| c.mesh.as_ref().map(|h| h.untyped_clone()))
.collect();
optionally_add_to_dep(&mut deps, &self.mesh);
deps
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -9,12 +9,10 @@ edition = "2021"
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
lyra-math = { path = "../lyra-math" }
lyra-scene = { path = "../lyra-scene" }
anyhow = "1.0.75"
base64 = "0.21.4"
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
glam = "0.29.0"
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] }
image = "0.25.2"
# not using custom matcher, or file type from file path
infer = { version = "0.15.0", default-features = false }

View File

@ -1,54 +0,0 @@
pub mod loader;
pub use loader::*;
pub mod material;
use lyra_reflect::Reflect;
use lyra_scene::SceneGraph;
use crate::ResourceData;
pub use material::*;
pub mod mesh;
pub use mesh::*;
pub mod scene;
pub use scene::*;
use crate::ResHandle;
use crate::lyra_engine;
/// A loaded Gltf file
#[derive(Clone, Default, Reflect)]
pub struct Gltf {
pub scenes: Vec<ResHandle<SceneGraph>>,
pub materials: Vec<ResHandle<Material>>,
pub meshes: Vec<ResHandle<Mesh>>,
}
impl ResourceData for Gltf {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
let mut deps = vec![];
for scene in self.scenes.iter() {
deps.push(scene.untyped_clone())
}
for mat in self.materials.iter() {
deps.push(mat.untyped_clone())
}
for mesh in self.meshes.iter() {
deps.push(mesh.untyped_clone())
}
deps
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

@ -1,113 +0,0 @@
use lyra_math::Transform;
use lyra_scene::SceneGraph;
use crate::{optionally_add_to_dep, ResourceData, UntypedResHandle};
use super::Mesh;
use crate::ResHandle;
/// A Node in the Gltf file
#[derive(Clone, Default)]
pub struct GltfNode {
pub name: Option<String>,
pub mesh: Option<ResHandle<Mesh>>,
pub transform: Transform,
pub children: Vec<GltfNode>,
}
impl ResourceData for GltfNode {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
let mut deps: Vec<UntypedResHandle> = self.children.iter()
.flat_map(|c| c.mesh.as_ref().map(|h| h.untyped_clone()))
.collect();
optionally_add_to_dep(&mut deps, &self.mesh);
deps
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
// A Scene in a Gltf file
/* #[derive(Clone)]
pub struct GltfScene {
pub nodes: Vec<GltfNode>,
}
impl ResourceData for GltfScene {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
let deps: Vec<UntypedResHandle> = self.nodes.iter()
.map(|n| n.dependencies())
.flatten()
.collect();
deps
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl GltfScene {
fn collect_node(&self, parent_node: &GltfNode, node: &GltfNode) -> Vec<(ResHandle<Mesh>, Transform)> {
let mut v = vec![];
if let Some(mesh) = &node.mesh {
v.push((mesh.clone(), parent_node.transform + node.transform));
}
for child in node.children.iter() {
let mut tmp = self.collect_node(node, child);
v.append(&mut tmp);
}
v
}
/// Collects all Gltf meshes and gets their world Transform.
pub fn collect_world_meshes(&self) -> Vec<(ResHandle<Mesh>, Transform)> {
let mut v = vec![];
// process the root nodes in the scene
for parent_node in self.nodes.iter() {
if let Some(mesh) = &parent_node.mesh {
v.push((mesh.clone(), parent_node.transform));
}
for child in parent_node.children.iter() {
let mut tmp = self.collect_node(parent_node, child);
v.append(&mut tmp);
}
}
v
}
} */
impl ResourceData for SceneGraph {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
self.world().view::<&crate::UntypedResHandle>()
.iter()
.map(|han| han.clone())
.collect()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

@ -12,8 +12,6 @@ pub use dep_state::*;
pub mod loader;
pub mod gltf;
mod world_ext;
pub use world_ext::*;

View File

@ -1,4 +1,4 @@
use std::{any::{Any, TypeId}, marker::PhantomData, ops::{Deref, DerefMut}, sync::{Arc, Condvar, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}};
use std::{any::{Any, TypeId}, marker::PhantomData, ops::{Deref, DerefMut}, sync::{Arc, Condvar, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration};
use lyra_ecs::Component;
use lyra_reflect::Reflect;
@ -198,17 +198,14 @@ impl UntypedResHandle {
/// This blocks the thread without consuming CPU time; its backed by a
/// [`Condvar`](std::sync::Condvar).
pub fn wait_for_load(&self) {
let d = self.read();
if matches!(d.state, ResourceState::Ready(_)) {
return;
self.wait_for_load_timeout_option_impl(None);
}
let cv = d.condvar.clone();
drop(d);
let l = cv.0.lock().unwrap();
let _unused = cv.1.wait(l).unwrap();
/// Does the same as [`UntypedResHandle::wait_for_load`] but has a timeout.
///
/// Returns true if the resource was loaded before hitting the timeout.
pub fn wait_for_load_timeout(&self, timeout: Duration) -> bool {
self.wait_for_load_timeout_option_impl(Some(timeout))
}
/// Wait for the entire resource, including its dependencies to be loaded.
@ -216,7 +213,41 @@ impl UntypedResHandle {
/// This blocks the thread without consuming CPU time; its backed by a
/// [`Condvar`](std::sync::Condvar).
pub fn wait_recurse_dependencies_load(&self) {
self.wait_for_load();
self.wait_recurse_dependencies_load_timeout_option_impl(None);
}
/// Does the same as [`UntypedResHandle::wait_recurse_dependencies_load`] but has a timeout.
///
/// Returns true if the resource was loaded before hitting the timeout.
pub fn wait_recurse_dependencies_load_timeout(&self, timeout: Duration) -> bool {
self.wait_recurse_dependencies_load_timeout_option_impl(Some(timeout))
}
fn wait_for_load_timeout_option_impl(&self, timeout: Option<Duration>) -> bool {
let d = self.read();
if matches!(d.state, ResourceState::Ready(_)) {
return true;
}
let cv = d.condvar.clone();
drop(d);
let l = cv.0.lock().unwrap();
if let Some(timeout) = timeout {
let (_unused, timeout) = cv.1.wait_timeout(l, timeout).unwrap();
!timeout.timed_out()
} else {
let _unused = cv.1.wait(l).unwrap();
true
}
}
fn wait_recurse_dependencies_load_timeout_option_impl(&self, timeout: Option<Duration>) -> bool {
if !self.wait_for_load_timeout_option_impl(timeout) {
return false;
}
let res = self.read();
match &res.state {
@ -226,11 +257,16 @@ impl UntypedResHandle {
// waiting for some resources and finish early.
while self.recurse_dependency_state().is_loading() {
for dep in data.recur_dependencies() {
dep.wait_for_load();
if !dep.wait_for_load_timeout_option_impl(timeout) {
return false;
}
}
}
true
},
_ => unreachable!() // the self.wait_for_load ensures that the state is ready
// self.wait_for_load at the start ensures that the state is ready
_ => unreachable!()
}
}

View File

@ -7,7 +7,7 @@ use notify_debouncer_full::{DebouncedEvent, FileIdMap};
use thiserror::Error;
use uuid::Uuid;
use crate::{gltf::ModelLoader, loader::{ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceData, ResourceState, UntypedResHandle};
use crate::{loader::{ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceData, ResourceState, UntypedResHandle};
/// A trait for type erased storage of a resource.
/// Implemented for [`ResHandle<T>`]
@ -82,7 +82,7 @@ impl Default for ResourceManager {
ResourceManagerState {
resources: HashMap::new(),
uuid_resources: HashMap::new(),
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
loaders: vec![ Arc::new(ImageLoader), ],
watchers: HashMap::new(),
}
))
@ -369,7 +369,7 @@ impl ResourceManager {
}
#[cfg(test)]
pub(crate) mod tests {
pub mod tests {
use std::{io, ops::Deref};
use instant::Instant;
@ -384,7 +384,7 @@ pub(crate) mod tests {
format!("{manifest}/test_files/img/{path}")
}
pub(crate) fn busy_wait_resource<R: ResourceData>(handle: &ResHandle<R>, timeout: f32) {
pub fn busy_wait_resource<R: ResourceData>(handle: &ResHandle<R>, timeout: f32) {
let start = Instant::now();
while !handle.is_loaded() {
// loop until the image is loaded
@ -398,7 +398,7 @@ pub(crate) mod tests {
}
}
pub(crate) fn busy_wait_resource_reload<R: ResourceData>(handle: &ResHandle<R>, timeout: f32) {
pub fn busy_wait_resource_reload<R: ResourceData>(handle: &ResHandle<R>, timeout: f32) {
let version = handle.version();
let start = Instant::now();

View File

@ -1,42 +1 @@
use base64::Engine;
use thiserror::Error;
use std::io;
#[allow(dead_code)]
#[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="resource/models"
pub(crate) fn gltf_read_buffer_uri(containing_path: &str, uri: &str) -> Result<Vec<u8>, UriReadError> {
if let Some((mime, data)) = uri.strip_prefix("data")
.and_then(|uri| 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)
.map_err(UriReadError::Base64Decode)
} else {
Ok(data.as_bytes().to_vec())
}
} else {
let full_path = format!("{containing_path}/{uri}");
std::fs::read(full_path).map_err(UriReadError::IoError)
}
}

View File

@ -10,3 +10,4 @@ anyhow = "1.0.81"
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-math = { path = "../lyra-math" }
lyra-reflect = { path = "../lyra-reflect" }
lyra-resource = { path = "../lyra-resource" }

View File

@ -1,5 +1,6 @@
mod node;
use lyra_reflect::Reflect;
use lyra_resource::{ResourceData, UntypedResHandle};
pub use node::*;
mod world_transform;
@ -135,6 +136,23 @@ impl SceneGraph {
}
}
impl ResourceData for SceneGraph {
fn dependencies(&self) -> Vec<UntypedResHandle> {
self.world().view::<&UntypedResHandle>()
.iter()
.map(|han| han.clone())
.collect()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
/// Add a node under a parent node.
///
/// The spawned entity will have a `ChildOf` relation targeting the provided parent node,

View File

@ -1,6 +1,6 @@
use std::{any::TypeId, ops::Deref};
use lyra_resource::{gltf::{Gltf, Material, Mesh}, FilterMode, ResHandle, Texture, WrappingMode};
use lyra_game::scene::SceneGraph;
use lyra_resource::{FilterMode, ResHandle, Texture, WrappingMode};
use lyra_game::{scene::SceneGraph, gltf::{Gltf, Material, Mesh, PbrGlossiness, Specular, MeshIndices, AlphaMode}};
use lyra_reflect::Reflect;
use lyra_scripting_derive::{lua_wrap_handle, wrap_lua_struct};
@ -58,7 +58,7 @@ wrap_lua_struct!(lyra_resource::TextureSampler,
}
);
wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness,
wrap_lua_struct!(PbrGlossiness,
// this can be safely skipped since it wont be a component or resource.
skip(lua_reflect),
fields={
@ -68,7 +68,7 @@ wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness,
}
);
wrap_lua_struct!(lyra_resource::gltf::Specular,
wrap_lua_struct!(Specular,
// this can be safely skipped since it wont be a component or resource.
skip(lua_reflect),
fields={
@ -104,13 +104,13 @@ lua_wrap_handle!(Mesh,
let table = lua.create_table()?;
match &data.indices {
Some(lyra_resource::gltf::MeshIndices::U16(v)) => {
Some(MeshIndices::U16(v)) => {
for (i, ind) in v.iter().enumerate() {
let i = i as i64 + 1; // lua indexes start at 1
table.raw_set(i, *ind)?;
}
},
Some(lyra_resource::gltf::MeshIndices::U32(v)) => {
Some(MeshIndices::U32(v)) => {
for (i, ind) in v.iter().enumerate() {
let i = i as i64 + 1; // lua indexes start at 1
table.raw_set(i, *ind)?;
@ -178,9 +178,9 @@ lua_wrap_handle!(Material,
alpha_cutoff,
(alpha_mode, {
match data.alpha_mode {
lyra_resource::gltf::AlphaMode::Opaque => "opaque",
lyra_resource::gltf::AlphaMode::Mask => "mask",
lyra_resource::gltf::AlphaMode::Blend => "blend",
AlphaMode::Opaque => "opaque",
AlphaMode::Mask => "mask",
AlphaMode::Blend => "blend",
}.into_lua(lua)
}),
(specular, {

View File

@ -1,5 +1,5 @@
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager}, ecs::query::View, game::App, input::{
assets::ResourceManager, gltf::Gltf, ecs::query::View, game::App, input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId,
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{

View File

@ -1,7 +1,7 @@
use std::ptr::NonNull;
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager},
assets::ResourceManager, gltf::Gltf,
ecs::{
query::{Res, View},
system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem},

View File

@ -1,5 +1,5 @@
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager}, game::App, input::{
assets::ResourceManager, gltf::Gltf, game::App, input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{

View File

@ -1,4 +1,4 @@
use lyra_engine::{assets::{gltf::Gltf, ResourceManager}, ecs::query::{Res, View}, game::App, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::light::{directional::DirectionalLight, PointLight}, scene::{CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN}, DeltaTime};
use lyra_engine::{assets::ResourceManager, gltf::Gltf, ecs::query::{Res, View}, game::App, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::light::{directional::DirectionalLight, PointLight}, scene::{CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN}, DeltaTime};
use rand::Rng;
use tracing::info;

View File

@ -1,5 +1,5 @@
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager},
assets::ResourceManager, gltf::Gltf,
game::App,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,

View File

@ -1,5 +1,5 @@
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager}, ecs::query::View, game::App, input::{
assets::ResourceManager, gltf::Gltf, ecs::query::View, game::App, input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId,
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{

View File

@ -2,7 +2,7 @@ use std::ptr::NonNull;
use lyra_engine::assets::ResourceManager;
use lyra_engine::{
assets::gltf::Gltf,
gltf::Gltf,
ecs::{
query::{Res, View},
system::{Criteria, CriteriaSchedule, IntoSystem},