Compare commits

..

10 Commits

36 changed files with 886 additions and 366 deletions

3
Cargo.lock generated
View File

@ -1845,7 +1845,9 @@ dependencies = [
"gltf", "gltf",
"image", "image",
"infer", "infer",
"instant",
"lyra-ecs", "lyra-ecs",
"lyra-math",
"mime", "mime",
"notify", "notify",
"notify-debouncer-full", "notify-debouncer-full",
@ -3101,7 +3103,6 @@ dependencies = [
"async-std", "async-std",
"fps_counter", "fps_counter",
"lyra-engine", "lyra-engine",
"lyra-scripting",
"tracing", "tracing",
] ]

View File

@ -6,8 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
lyra-engine = { path = "../../", version = "0.0.1", features = ["lua_scripting"] } lyra-engine = { path = "../../", version = "0.0.1" }
lyra-scripting = { path = "../../lyra-scripting", features = ["lua", "teal"] } #lyra-engine = { path = "../../", version = "0.0.1", features = ["lua_scripting"] }
#lyra-scripting = { path = "../../lyra-scripting", features = ["lua", "teal"] }
#lyra-ecs = { path = "../../lyra-ecs"} #lyra-ecs = { path = "../../lyra-ecs"}
anyhow = "1.0.75" anyhow = "1.0.75"
async-std = "1.12.0" async-std = "1.12.0"

View File

@ -0,0 +1,2 @@
Sponza*
Textures

View File

@ -0,0 +1 @@
To keep the size of this repository down, the Sponza scene is omitted from this repo. The files were downloaded from https://github.com/toji/sponza-optimized

View File

@ -1,7 +1,7 @@
use std::ptr::NonNull; use std::ptr::NonNull;
use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, World, Component}, DeltaTime, scene::{ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList}; use lyra_engine::{assets::gltf::Gltf, ecs::{system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Transform, Vec3}, render::light::{directional::DirectionalLight, PointLight, SpotLight}, scene::{CameraComponent, MeshComponent}, DeltaTime};
use lyra_engine::assets::{ResourceManager, Model}; use lyra_engine::assets::ResourceManager;
mod free_fly_camera; mod free_fly_camera;
use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera}; use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera};
@ -16,6 +16,13 @@ pub enum ActionLabel {
LookRoll, LookRoll,
} }
const ACTLBL_MOVE_UP_DOWN: &str = "MoveUpDown";
const ACTLBL_MOVE_LEFT_RIGHT: &str = "MoveLeftRight";
const ACTLBL_MOVE_FORWARD_BACKWARD: &str = "MoveForwardBackward";
const ACTLBL_LOOK_LEFT_RIGHT: &str = "LookLeftRight";
const ACTLBL_LOOK_UP_DOWN: &str = "LookUpDown";
const ACTLBL_LOOK_ROLL: &str = "LookRoll";
struct FixedTimestep { struct FixedTimestep {
max_tps: u32, max_tps: u32,
fixed_time: f32, fixed_time: f32,
@ -86,30 +93,45 @@ async fn main() {
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap(); //let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
//let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap(); //let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap();
//let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap(); //let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap();
let cube_model = resman.request::<Model>("assets/texture-sep/texture-sep.gltf").unwrap(); /* let cube_gltf = resman.request::<Gltf>("assets/texture-sep/texture-sep.gltf").unwrap();
let crate_model = resman.request::<Model>("assets/crate/crate.gltf").unwrap(); let crate_gltf = resman.request::<Gltf>("assets/crate/crate.gltf").unwrap();
//let sponza_model = resman.request::<Model>("assets/sponza/Sponza.gltf").unwrap();
let separate_gltf = resman.request::<Gltf>("assets/pos-testing/child-node-cubes.glb").unwrap(); */
//drop(resman);
/* let cube_mesh = &cube_gltf.data_ref()
.unwrap().meshes[0];
let crate_mesh = &crate_gltf.data_ref()
.unwrap().meshes[0];
let separate_scene = &separate_gltf.data_ref()
.unwrap().scenes[0]; */
let sponza_model = resman.request::<Gltf>("assets/sponza/Sponza.gltf").unwrap();
drop(resman); drop(resman);
let sponza_scene = &sponza_model.data_ref()
.unwrap().scenes[0];
world.spawn((
sponza_scene.clone(),
Transform::from_xyz(0.0, 0.0, 0.0),
));
/* world.spawn(( /* world.spawn((
ModelComponent(antique_camera_model), separate_scene.clone(),
Transform::from_xyz(0.0, -5.0, -10.0), Transform::from_xyz(0.0, -5.0, -10.0),
)); */ )); */
/* world.spawn(( /* {
ModelComponent(sponza_model), let cube_tran = Transform::from_xyz(-5.9026427, -1.8953488, -10.0);
Transform::from_xyz(0.0, 0.0, 0.0),
)); */
{
let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0);
//cube_tran.rotate_y(math::Angle::Degrees(180.0)); //cube_tran.rotate_y(math::Angle::Degrees(180.0));
world.spawn(( world.spawn((
cube_tran, cube_tran,
ModelComponent(crate_model.clone()), crate_mesh.clone(),
CubeFlag, CubeFlag,
)); ));
} } */
{ {
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0); let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
@ -124,11 +146,11 @@ async fn main() {
specular: 1.3, specular: 1.3,
}, },
light_tran, light_tran,
ModelComponent(cube_model.clone()), //cube_mesh.clone(),
)); ));
} }
{ /* {
let mut light_tran = Transform::from_xyz(-3.5, 0.2, -4.5); let mut light_tran = Transform::from_xyz(-3.5, 0.2, -4.5);
light_tran.scale = Vec3::new(0.5, 0.5, 0.5); light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
world.spawn(( world.spawn((
@ -146,9 +168,31 @@ async fn main() {
specular: 1.0, specular: 1.0,
}, },
Transform::from(light_tran), Transform::from(light_tran),
ModelComponent(cube_model.clone()), cube_mesh.clone(),
)); ));
} }
{
let mut light_tran = Transform::from_xyz(2.0, 2.5, -9.5);
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
world.spawn((
PointLight {
color: Vec3::new(0.0, 0.0, 1.0),
intensity: 3.3,
constant: 1.0,
linear: 0.09,
quadratic: 0.032,
ambient: 0.2,
diffuse: 1.0,
specular: 1.3,
},
Transform::from(light_tran),
cube_mesh.clone(),
));
} */
/* { /* {
let mut light_tran = Transform::from_xyz(-5.0, 2.5, -9.5); let mut light_tran = Transform::from_xyz(-5.0, 2.5, -9.5);
@ -172,28 +216,6 @@ async fn main() {
)); ));
} */ } */
{
let mut light_tran = Transform::from_xyz(2.0, 2.5, -9.5);
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
world.spawn((
PointLight {
color: Vec3::new(0.0, 0.0, 1.0),
intensity: 3.3,
constant: 1.0,
linear: 0.09,
quadratic: 0.032,
ambient: 0.2,
diffuse: 1.0,
specular: 1.3,
},
Transform::from(light_tran),
ModelComponent(cube_model),
));
}
let mut camera = CameraComponent::new_3d(); let mut camera = CameraComponent::new_3d();
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5); camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
world.spawn(( camera, FreeFlyCamera::default() )); world.spawn(( camera, FreeFlyCamera::default() ));
@ -211,7 +233,7 @@ async fn main() {
Ok(()) Ok(())
}; };
let fps_plugin = move |game: &mut Game| { let _fps_plugin = move |game: &mut Game| {
let world = game.world_mut(); let world = game.world_mut();
world.add_resource(fps_counter::FPSCounter::new()); world.add_resource(fps_counter::FPSCounter::new());
}; };
@ -246,54 +268,54 @@ async fn main() {
}; };
let action_handler_plugin = |game: &mut Game| { let action_handler_plugin = |game: &mut Game| {
/* let action_handler = ActionHandler::builder() let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0)) .add_layout(LayoutId::from(0))
.add_action(CommonActionLabel::MoveForwardBackward, Action::new(ActionKind::Axis)) .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
.add_action(CommonActionLabel::MoveLeftRight, Action::new(ActionKind::Axis)) .add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(CommonActionLabel::MoveUpDown, Action::new(ActionKind::Axis)) .add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(CommonActionLabel::LookLeftRight, Action::new(ActionKind::Axis)) .add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(CommonActionLabel::LookUpDown, Action::new(ActionKind::Axis)) .add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(CommonActionLabel::LookRoll, Action::new(ActionKind::Axis)) .add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
.add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0)) .add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
.bind(CommonActionLabel::MoveForwardBackward, &[ .bind(ACTLBL_MOVE_FORWARD_BACKWARD, &[
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0)
]) ])
.bind(CommonActionLabel::MoveLeftRight, &[ .bind(ACTLBL_MOVE_LEFT_RIGHT, &[
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0), ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0) ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0)
]) ])
.bind(CommonActionLabel::MoveUpDown, &[ .bind(ACTLBL_MOVE_UP_DOWN, &[
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0) ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0)
]) ])
.bind(CommonActionLabel::LookLeftRight, &[ .bind(ACTLBL_LOOK_LEFT_RIGHT, &[
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(), ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0), ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickX)).into_binding(), //ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickX)).into_binding(),
]) ])
.bind(CommonActionLabel::LookUpDown, &[ .bind(ACTLBL_LOOK_UP_DOWN, &[
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(), ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0), ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickY)).into_binding(), //ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickY)).into_binding(),
]) ])
.bind(CommonActionLabel::LookRoll, &[ .bind(ACTLBL_LOOK_ROLL, &[
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0), ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
]) ])
.finish() .finish()
); ).finish();
let world = game.world_mut(); let world = game.world_mut();
world.add_resource(action_handler); */ world.add_resource(action_handler);
game.with_plugin(InputActionPlugin); game.with_plugin(InputActionPlugin);
}; };
let script_test_plugin = |game: &mut Game| { /* let script_test_plugin = |game: &mut Game| {
game.with_plugin(LuaScriptingPlugin); game.with_plugin(LuaScriptingPlugin);
let world = game.world_mut(); let world = game.world_mut();
@ -306,13 +328,13 @@ async fn main() {
let scripts = ScriptList::new(vec![script]); let scripts = ScriptList::new(vec![script]);
world.spawn((scripts,)); world.spawn((scripts,));
}; }; */
Game::initialize().await Game::initialize().await
.with_plugin(lyra_engine::DefaultPlugins) .with_plugin(lyra_engine::DefaultPlugins)
.with_startup_system(setup_sys.into_system()) .with_startup_system(setup_sys.into_system())
.with_plugin(action_handler_plugin) .with_plugin(action_handler_plugin)
.with_plugin(script_test_plugin) //.with_plugin(script_test_plugin)
//.with_plugin(fps_plugin) //.with_plugin(fps_plugin)
.with_plugin(jiggle_plugin) .with_plugin(jiggle_plugin)
.with_plugin(FreeFlyCameraPlugin) .with_plugin(FreeFlyCameraPlugin)

View File

@ -177,10 +177,10 @@ where
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
let col = archetype.get_column(self.type_id) let col = archetype.get_column(self.type_id)
.expect("You ignored 'can_visit_archetype'!"); .expect("You ignored 'can_visit_archetype'!");
let col = NonNull::from(col);
// TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated. // TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated.
// the fetcher needs to tick the entities tick in the archetype // the fetcher needs to tick the entities tick in the archetype
let col = NonNull::from(col);
FetchBorrowMut { FetchBorrowMut {
col, col,

View File

@ -0,0 +1,40 @@
use std::marker::PhantomData;
use crate::{query::{AsQuery, Query}, Archetype, Component, DynTypeId, World};
#[derive(Default)]
pub struct Has<C: Component> {
_marker: PhantomData<C>
}
impl<C: Component> Copy for Has<C> {}
impl<C: Component> Clone for Has<C> {
fn clone(&self) -> Self {
Self { _marker: self._marker.clone() }
}
}
impl<C: Component> Query for Has<C> {
type Item<'a> = ();
type Fetch<'a> = ();
fn new() -> Self {
Has {
_marker: PhantomData
}
}
fn can_visit_archetype(&self, archetype: &Archetype) -> bool {
archetype.has_column(DynTypeId::of::<C>())
}
unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> {
()
}
}
impl<C: Component> AsQuery for Has<C> {
type Query = Self;
}

View File

@ -0,0 +1,5 @@
pub mod has_component;
pub use has_component::*;
pub mod or;
pub use or::*;

View File

@ -0,0 +1,40 @@
use crate::{query::{AsQuery, Query}, Archetype, World};
#[derive(Default)]
pub struct Or<Q1: AsQuery, Q2: AsQuery> {
left: Q1::Query,
right: Q2::Query,
}
impl<Q1: AsQuery, Q2: AsQuery> Copy for Or<Q1, Q2> {}
impl<Q1: AsQuery, Q2: AsQuery> Clone for Or<Q1, Q2> {
fn clone(&self) -> Self {
Self { left: self.left.clone(), right: self.right.clone() }
}
}
impl<Q1: AsQuery, Q2: AsQuery> Query for Or<Q1, Q2> {
type Item<'a> = ();
type Fetch<'a> = ();
fn new() -> Self {
Or {
left: Q1::Query::new(),
right: Q2::Query::new(),
}
}
fn can_visit_archetype(&self, archetype: &Archetype) -> bool {
self.left.can_visit_archetype(archetype) || self.right.can_visit_archetype(archetype)
}
unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> {
()
}
}
impl<Q1: AsQuery, Q2: AsQuery> AsQuery for Or<Q1, Q2> {
type Query = Self;
}

View File

@ -29,6 +29,8 @@ pub use world::*;
pub mod dynamic; pub mod dynamic;
pub mod filter;
/// A [`Fetch`]er implementation gets data out of an archetype. /// A [`Fetch`]er implementation gets data out of an archetype.
pub trait Fetch<'a> { pub trait Fetch<'a> {
/// The type that this Fetch yields /// The type that this Fetch yields

View File

@ -90,7 +90,7 @@ where
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
loop { loop {
if Q::ALWAYS_FETCHES && F::ALWAYS_FETCHES { if Q::ALWAYS_FETCHES {
// only fetch this query once. // only fetch this query once.
// fetcher gets set to Some after this `next` call. // fetcher gets set to Some after this `next` call.
if self.fetcher.is_none() { if self.fetcher.is_none() {

View File

@ -254,6 +254,13 @@ impl World {
v.into_iter() v.into_iter()
} }
/// View into the world for a set of entities that satisfy the queries.
pub fn filtered_view_iter<Q: AsQuery, F: AsQuery>(&self) -> ViewIter<Q::Query, F::Query> {
let archetypes = self.archetypes.values().collect();
let v = ViewState::new(self, Q::Query::new(), F::Query::new(), archetypes);
v.into_iter()
}
pub fn dynamic_view(&self) -> DynamicView { pub fn dynamic_view(&self) -> DynamicView {
DynamicView::new(self) DynamicView::new(self)
} }

View File

@ -1,5 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use lyra_resource::{ResHandle, Texture};
use super::texture::RenderTexture; use super::texture::RenderTexture;
pub struct MaterialSpecular { pub struct MaterialSpecular {
@ -9,13 +11,18 @@ pub struct MaterialSpecular {
pub color_texture: Option<RenderTexture>, pub color_texture: Option<RenderTexture>,
} }
impl MaterialSpecular { fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Arc<wgpu::BindGroupLayout>, i: &Option<ResHandle<Texture>>) -> Option<RenderTexture> {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::Specular) -> Self { if let Some(tex) = i {
let tex = value.texture.as_ref().map(|t| t.data_ref()) RenderTexture::from_resource(device, queue, bg_layout.clone(), tex, None).ok()
.map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap()); } else {
None
}
}
let color_tex = value.color_texture.as_ref().map(|t| t.data_ref()) impl MaterialSpecular {
.map(|i| RenderTexture::from_image(device, queue, bg_layout, &i.image, None).unwrap()); pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::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);
Self { Self {
factor: value.factor, factor: value.factor,
@ -38,9 +45,8 @@ pub struct Material {
} }
impl Material { impl Material {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::Material) -> Self { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Material) -> Self {
let diffuse_texture = value.base_color_texture.as_ref().map(|t| t.data_ref()) let diffuse_texture = texture_to_render(device, queue, &bg_layout, &value.base_color_texture);
.map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap());
let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s)); let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s));

View File

@ -1,21 +1,17 @@
use lyra_ecs::Entity; use lyra_ecs::Entity;
use crate::math::Transform;
pub struct RenderJob { pub struct RenderJob {
pub entity: Entity, pub entity: Entity,
pub shader_id: u64, pub shader_id: u64,
pub mesh_buffer_id: uuid::Uuid, pub mesh_uuid: uuid::Uuid,
pub transform: Transform,
} }
impl RenderJob { impl RenderJob {
pub fn new(entity: Entity, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform) -> Self { pub fn new(entity: Entity, shader_id: u64, mesh_buffer_id: uuid::Uuid) -> Self {
Self { Self {
entity, entity,
shader_id, shader_id,
mesh_buffer_id, mesh_uuid: mesh_buffer_id,
transform,
} }
} }
} }

View File

@ -5,10 +5,13 @@ use std::borrow::Cow;
use glam::Vec3; use glam::Vec3;
use instant::Instant; use instant::Instant;
use itertools::izip; use itertools::izip;
use lyra_ecs::Entity; use lyra_ecs::query::filter::{Has, Or};
use lyra_ecs::{Entity, Tick};
use lyra_ecs::query::{Entities, TickOf}; use lyra_ecs::query::{Entities, TickOf};
use lyra_ecs::World; use lyra_ecs::World;
use lyra_resource::gltf::GltfScene;
use tracing::{debug, warn}; use tracing::{debug, warn};
use uuid::Uuid;
use wgpu::{BindGroupLayout, Limits}; use wgpu::{BindGroupLayout, Limits};
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::window::Window; use winit::window::Window;
@ -16,7 +19,7 @@ use winit::window::Window;
use crate::math::Transform; use crate::math::Transform;
use crate::render::material::MaterialUniform; use crate::render::material::MaterialUniform;
use crate::render::render_buffer::BufferWrapperBuilder; use crate::render::render_buffer::BufferWrapperBuilder;
use crate::scene::{ModelComponent, CameraComponent}; use crate::scene::CameraComponent;
use super::camera::{RenderCamera, CameraUniform}; use super::camera::{RenderCamera, CameraUniform};
use super::desc_buf_lay::DescVertexBufferLayout; use super::desc_buf_lay::DescVertexBufferLayout;
@ -28,7 +31,10 @@ use super::transform_buffer_storage::TransformBuffers;
use super::vertex::Vertex; 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::{gltf::Mesh, ResHandle};
type MeshHandle = ResHandle<Mesh>;
type SceneHandle = ResHandle<GltfScene>;
pub trait Renderer { pub trait Renderer {
fn prepare(&mut self, main_world: &mut World); fn prepare(&mut self, main_world: &mut World);
@ -265,16 +271,19 @@ impl BasicRenderer {
s s
} }
fn update_mesh_buffers(&mut self, _entity: Entity, mesh: &Mesh) { /// Checks if the mesh buffers in the GPU need to be updated.
if let Some(buffers) = self.mesh_buffers.get_mut(&mesh.uuid) { fn check_mesh_buffers(&mut self, _entity: Entity, meshh: &ResHandle<Mesh>) {
let mesh_uuid = meshh.uuid();
if let (Some(mesh), Some(buffers)) = (meshh.data_ref(), self.mesh_buffers.get_mut(&mesh_uuid)) {
// check if the buffer sizes dont match. If they dont, completely remake the buffers // check if the buffer sizes dont match. If they dont, completely remake the buffers
let vertices = mesh.position().unwrap(); let vertices = mesh.position().unwrap();
if buffers.buffer_vertex.count() != vertices.len() { if buffers.buffer_vertex.count() != vertices.len() {
debug!("Recreating buffers for mesh {}", mesh.uuid.to_string()); debug!("Recreating buffers for mesh {}", mesh_uuid.to_string());
let (vert, idx) = self.create_vertex_index_buffers(mesh); let (vert, idx) = self.create_vertex_index_buffers(&mesh);
// have to re-get buffers because of borrow checker // have to re-get buffers because of borrow checker
let buffers = self.mesh_buffers.get_mut(&mesh.uuid).unwrap(); let buffers = self.mesh_buffers.get_mut(&mesh_uuid).unwrap();
buffers.buffer_indices = idx; buffers.buffer_indices = idx;
buffers.buffer_vertex = vert; buffers.buffer_vertex = vert;
@ -292,8 +301,8 @@ impl BasicRenderer {
if let Some(index_buffer) = buffers.buffer_indices.as_ref() { if let Some(index_buffer) = buffers.buffer_indices.as_ref() {
let aligned_indices = match mesh.indices.as_ref().unwrap() { 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. // 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::<u16, u32>(v).1, lyra_resource::gltf::MeshIndices::U16(v) => bytemuck::pod_align_to::<u16, u32>(v).1,
lyra_resource::MeshIndices::U32(v) => bytemuck::pod_align_to::<u32, u32>(v).1, lyra_resource::gltf::MeshIndices::U32(v) => bytemuck::pod_align_to::<u32, u32>(v).1,
}; };
let index_buffer = index_buffer.1.buffer(); let index_buffer = index_buffer.1.buffer();
@ -327,8 +336,8 @@ impl BasicRenderer {
let indices = match mesh.indices.as_ref() { let indices = match mesh.indices.as_ref() {
Some(indices) => { Some(indices) => {
let (idx_type, len, contents) = match indices { let (idx_type, len, contents) = match indices {
lyra_resource::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)), lyra_resource::gltf::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)), lyra_resource::gltf::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)),
}; };
let index_buffer = self.device.create_buffer_init( let index_buffer = self.device.create_buffer_init(
@ -354,7 +363,11 @@ impl BasicRenderer {
fn create_mesh_buffers(&mut self, mesh: &Mesh) -> MeshBufferStorage { fn create_mesh_buffers(&mut self, mesh: &Mesh) -> MeshBufferStorage {
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh); let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh);
let material = Material::from_resource(&self.device, &self.queue, self.bgl_texture.clone(), &mesh.material()); let material = mesh.material.as_ref()
.expect("Material resource not loaded yet")
.data_ref()
.unwrap();
let material = Material::from_resource(&self.device, &self.queue, self.bgl_texture.clone(), &material);
let uni = MaterialUniform::from(&material); let uni = MaterialUniform::from(&material);
self.queue.write_buffer(&self.material_buffer.inner_buf, 0, bytemuck::bytes_of(&uni)); self.queue.write_buffer(&self.material_buffer.inner_buf, 0, bytemuck::bytes_of(&uni));
debug!("Wrote material to buffer"); debug!("Wrote material to buffer");
@ -367,24 +380,57 @@ impl BasicRenderer {
} }
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
fn process_mesh(&mut self, entity: Entity, transform: Transform, mesh: &Mesh) -> bool { fn process_mesh(&mut self, entity: Entity, transform: Transform, mesh: &Mesh, mesh_uuid: Uuid) -> bool {
if self.transform_buffers.should_expand() { if self.transform_buffers.should_expand() {
self.transform_buffers.expand_buffers(&self.device); self.transform_buffers.expand_buffers(&self.device);
} }
self.transform_buffers.update_or_insert(&self.queue, &self.render_limits, self.transform_buffers.update_or_insert(&self.queue, &self.render_limits,
entity, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) )); mesh_uuid, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) ));
#[allow(clippy::map_entry)] #[allow(clippy::map_entry)]
if !self.mesh_buffers.contains_key(&mesh.uuid) { if !self.mesh_buffers.contains_key(&mesh_uuid) {
// create the mesh's buffers // create the mesh's buffers
let buffers = self.create_mesh_buffers(mesh); let buffers = self.create_mesh_buffers(mesh);
self.mesh_buffers.insert(mesh.uuid, buffers); self.mesh_buffers.insert(mesh_uuid, buffers);
self.entity_meshes.insert(entity, mesh.uuid); self.entity_meshes.insert(entity, mesh_uuid);
true true
} else { false } } else { false }
} }
fn interpolate_transforms(&mut self, now: Instant, last_epoch: Tick, entity: Entity, transform: &Transform, transform_epoch: Tick) -> Transform {
let cached = match self.entity_last_transforms.get_mut(&entity) {
Some(last) if transform_epoch == last_epoch => {
last.from_transform = last.to_transform;
last.to_transform = *transform;
last.last_updated_at = Some(last.cached_at);
last.cached_at = now;
last.clone()
},
Some(last) => last.clone(),
None => {
let cached = CachedTransform {
last_updated_at: None,
cached_at: now,
from_transform: *transform,
to_transform: *transform,
};
self.entity_last_transforms.insert(entity, cached.clone());
cached
}
};
let fixed_time = match cached.last_updated_at {
Some(last_updated_at) => cached.cached_at - last_updated_at,
None => now - cached.cached_at
}.as_secs_f32();
let accumulator = (now - cached.cached_at).as_secs_f32();
let alpha = accumulator / fixed_time;
cached.from_transform.lerp(cached.to_transform, alpha)
}
} }
impl Renderer for BasicRenderer { impl Renderer for BasicRenderer {
@ -395,57 +441,62 @@ impl Renderer for BasicRenderer {
let now_inst = Instant::now(); let now_inst = Instant::now();
for (entity, model, model_epoch, transform, transform_epoch) in main_world.view_iter::<(Entities, &ModelComponent, TickOf<ModelComponent>, &Transform, TickOf<Transform>)>() { let view = main_world.filtered_view_iter::<(Entities, &Transform, TickOf<Transform>), Or<Has<MeshHandle>, Has<SceneHandle>>>();
for (entity, transform, transform_epoch) in view {
alive_entities.insert(entity); alive_entities.insert(entity);
let cached = match self.entity_last_transforms.get_mut(&entity) { let mesh_view = main_world.view_one::<(&MeshHandle, TickOf<MeshHandle>)>(entity);
Some(last) if transform_epoch == last_epoch => { if let Some((mesh_han, mesh_epoch)) = mesh_view.get() {
last.from_transform = last.to_transform; let interop_pos = self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch);
last.to_transform = *transform;
last.last_updated_at = Some(last.cached_at);
last.cached_at = now_inst;
last.clone() if let Some(mesh) = mesh_han.data_ref() {
}, // if process mesh did not just create a new mesh, and the epoch
Some(last) => last.clone(), // shows that the scene has changed, verify that the mesh buffers
None => { // dont need to be resent to the gpu.
let cached = CachedTransform { if !self.process_mesh(entity, interop_pos, &*mesh, mesh_han.uuid())
last_updated_at: None, && mesh_epoch == last_epoch {
cached_at: now_inst, self.check_mesh_buffers(entity, &mesh_han);
from_transform: *transform, }
to_transform: *transform,
}; let material = mesh.material.as_ref().unwrap()
self.entity_last_transforms.insert(entity, cached.clone()); .data_ref().unwrap();
cached let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid());
self.render_jobs.push_back(job);
} }
}; }
//debug!("Transform: {:?}, comp: {:?}", cached.to_transform.translation, transform.transform.translation);
let fixed_time = match cached.last_updated_at {
Some(last_updated_at) => cached.cached_at - last_updated_at,
None => now_inst - cached.cached_at
}.as_secs_f32();
let accumulator = (now_inst - cached.cached_at).as_secs_f32();
let alpha = accumulator / fixed_time;
let transform_val = cached.from_transform.lerp(cached.to_transform, alpha); let scene_view = main_world.view_one::<(&SceneHandle, TickOf<SceneHandle>)>(entity);
if let Some((scene_han, scene_epoch)) = scene_view.get() {
// TODO: Rendering scenes
let model = model.data_ref(); if let Some(scene) = scene_han.data_ref() {
for mesh in model.meshes.iter() { let interpo_pos = self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch);
if !self.process_mesh(entity, transform_val, mesh) && model_epoch == last_epoch { let meshes = scene.collect_world_meshes();
self.update_mesh_buffers(entity, mesh);
for (mesh_han, mesh_pos) in meshes.into_iter() {
if let Some(mesh) = mesh_han.data_ref() {
let mesh_interpo = interpo_pos + mesh_pos;
// if process mesh did not just create a new mesh, and the epoch
// shows that the scene has changed, verify that the mesh buffers
// dont need to be resent to the gpu.
if !self.process_mesh(entity, mesh_interpo, &*mesh, mesh_han.uuid())
&& scene_epoch == last_epoch {
self.check_mesh_buffers(entity, &mesh_han);
}
let material = mesh.material.as_ref().unwrap()
.data_ref().unwrap();
let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid());
self.render_jobs.push_back(job);
}
}
} }
let shader = mesh.material().shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh.uuid, transform_val);
self.render_jobs.push_back(job);
} }
} }
/* for (entity, mesh, mesh_epoch, transform) in main_world.query::<(Entities, &MeshComponent, EpochOf<MeshComponent>, &TransformComponent)>().iter() {
debug!("TODO: Process MeshComponents"); // TODO: Process MeshComponents
} */
// collect dead entities // collect dead entities
self.transform_buffers.tick(); self.transform_buffers.tick();
@ -513,7 +564,7 @@ impl Renderer for BasicRenderer {
render_pass.set_pipeline(pipeline.get_wgpu_pipeline()); render_pass.set_pipeline(pipeline.get_wgpu_pipeline());
// get the mesh (containing vertices) and the buffers from storage // get the mesh (containing vertices) and the buffers from storage
let buffers = self.mesh_buffers.get(&job.mesh_buffer_id).unwrap(); let buffers = self.mesh_buffers.get(&job.mesh_uuid).unwrap();
// Bind the optional texture // Bind the optional texture
if let Some(tex) = buffers.material.as_ref() if let Some(tex) = buffers.material.as_ref()
@ -532,7 +583,7 @@ impl Renderer for BasicRenderer {
} }
// Get the bindgroup for job's transform and bind to it using an offset. // Get the bindgroup for job's transform and bind to it using an offset.
let transform_indices = *self.transform_buffers.entity_indices(job.entity).unwrap(); let transform_indices = *self.transform_buffers.transform_indices(job.mesh_uuid).unwrap();
let bindgroup = self.transform_buffers.bind_group(transform_indices).unwrap(); let bindgroup = self.transform_buffers.bind_group(transform_indices).unwrap();
let offset = TransformBuffers::index_offset(&self.render_limits, transform_indices) as u32; let offset = TransformBuffers::index_offset(&self.render_limits, transform_indices) as u32;
render_pass.set_bind_group(1, bindgroup, &[ offset, offset, ]); render_pass.set_bind_group(1, bindgroup, &[ offset, offset, ]);

View File

@ -1,29 +1,10 @@
use std::sync::Arc; use std::sync::Arc;
use image::GenericImageView; use image::GenericImageView;
use lyra_resource::{ResHandle, Texture}; use lyra_resource::{FilterMode, ResHandle, Texture, WrappingMode};
use super::render_buffer::BindGroupPair; use super::render_buffer::BindGroupPair;
/* #[derive(Clone)]
pub struct Texture {
texture_id: u32,
pub img: image::DynamicImage,
}
impl Texture {
pub fn from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
let img = image::load_from_memory(bytes)?;
Ok(Self {
texture_id: 0,
img,
})
}
} */
#[allow(dead_code)] #[allow(dead_code)]
pub struct RenderTexture { pub struct RenderTexture {
pub inner_texture: wgpu::Texture, pub inner_texture: wgpu::Texture,
@ -153,8 +134,101 @@ impl RenderTexture {
}) })
} }
pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &Arc<ResHandle<Texture>>) { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, texture_res: &ResHandle<Texture>, label: Option<&str>) -> anyhow::Result<Self> {
let texture = &texture.data_ref().image; let texture_ref = texture_res.data_ref().unwrap();
let img = texture_ref.image.data_ref().unwrap();
let rgba = img.to_rgba8();
let dimensions = img.dimensions();
let size = wgpu::Extent3d {
width: dimensions.0,
height: dimensions.1,
depth_or_array_layers: 1,
};
let texture = device.create_texture(
&wgpu::TextureDescriptor {
label,
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
}
);
queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&rgba,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0),
rows_per_image: std::num::NonZeroU32::new(dimensions.1),
},
size,
);
// convert resource sampler into wgpu sampler
let sampler_desc = match &texture_ref.sampler {
Some(sampler) => {
let magf = res_filter_to_wgpu(sampler.mag_filter.unwrap_or(FilterMode::Linear));
let minf = res_filter_to_wgpu(sampler.min_filter.unwrap_or(FilterMode::Nearest));
let mipf = res_filter_to_wgpu(sampler.mipmap_filter.unwrap_or(FilterMode::Nearest));
let wrap_u = res_wrap_to_wgpu(sampler.wrap_u);
let wrap_v = res_wrap_to_wgpu(sampler.wrap_v);
let wrap_w = res_wrap_to_wgpu(sampler.wrap_w);
wgpu::SamplerDescriptor {
address_mode_u: wrap_u,
address_mode_v: wrap_v,
address_mode_w: wrap_w,
mag_filter: magf,
min_filter: minf,
mipmap_filter: mipf,
..Default::default()
}
}
None => wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
},
};
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(
&sampler_desc
);
let bgp = Self::create_bind_group_pair(device, bg_layout, &view, &sampler);
Ok(Self {
inner_texture: texture,
view,
sampler,
bindgroup_pair: Some(bgp),
})
}
/// Updates the texture on the gpu with the provided texture.
///
/// # Panics
/// Panics if `texture` is not loaded
pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &ResHandle<Texture>) {
let texture = &texture.data_ref().unwrap().image;
let texture = &texture.data_ref().unwrap();
let rgba = texture.to_rgba8(); let rgba = texture.to_rgba8();
let dimensions = texture.dimensions(); let dimensions = texture.dimensions();
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
@ -230,4 +304,21 @@ impl RenderTexture {
pub fn bind_group(&self) -> &wgpu::BindGroup { pub fn bind_group(&self) -> &wgpu::BindGroup {
&self.bindgroup_pair.as_ref().unwrap().bindgroup &self.bindgroup_pair.as_ref().unwrap().bindgroup
} }
}
/// Convert [`lyra_resource::WrappingMode`] to [`wgpu::AddressMode`]
fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode {
match wmode {
WrappingMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
WrappingMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,
WrappingMode::Repeat => wgpu::AddressMode::Repeat,
}
}
/// Convert [`lyra_resource::FilterMode`] to [`wgpu::FilterMode`]
fn res_filter_to_wgpu(fmode: FilterMode) -> wgpu::FilterMode {
match fmode {
FilterMode::Nearest => wgpu::FilterMode::Nearest,
FilterMode::Linear => wgpu::FilterMode::Linear,
}
} }

View File

@ -1,6 +1,6 @@
use std::{collections::{VecDeque, HashMap}, num::NonZeroU64}; use std::{collections::{VecDeque, HashMap}, num::NonZeroU64};
use lyra_ecs::Entity; use uuid::Uuid;
use wgpu::Limits; use wgpu::Limits;
use std::mem; use std::mem;
@ -19,11 +19,20 @@ pub(crate) struct TransformBufferEntry {
pub bindgroup: wgpu::BindGroup, pub bindgroup: wgpu::BindGroup,
} }
/// A helper struct for managing the Transform buffers for meshes.
///
/// This struct manages a "chain" of uniform buffers that store Transform for meshes. When
/// first created it only has a single "chain-link" with a buffer that is the maximum length
/// the GPU supports. When the first buffer fills up, a new one should be created which will also
/// be the maximum length the GPU supports. When the new buffer fills up, a new one will be
/// created once again, and so on.
///
/// `Uuid`'s are used to represent entries (usually Meshes) in the buffer. The Uuid's can be used
/// to insert, update, and retrieve the transforms.
pub(crate) struct TransformBuffers { pub(crate) struct TransformBuffers {
pub bindgroup_layout: wgpu::BindGroupLayout, pub bindgroup_layout: wgpu::BindGroupLayout,
/// A vector storing the EntityId and pub just_updated: HashMap<Uuid, TransformBufferIndices>,
pub just_updated: HashMap<Entity, TransformBufferIndices>, pub not_updated: HashMap<Uuid, TransformBufferIndices>,
pub not_updated: HashMap<Entity, TransformBufferIndices>,
pub dead_indices: VecDeque<TransformBufferIndices>, pub dead_indices: VecDeque<TransformBufferIndices>,
pub next_indices: TransformBufferIndices, pub next_indices: TransformBufferIndices,
/// (transform count, buffer, bindgroup) /// (transform count, buffer, bindgroup)
@ -78,12 +87,15 @@ impl TransformBuffers {
s s
} }
/// Update an entity's buffer with the new transform. Will panic if the entity isn't stored /// Update an transform in the buffer.
pub fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: Entity, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { ///
let indices = self.not_updated.remove(&entity) /// # Panics
.or_else(|| self.just_updated.remove(&entity)) /// Panics if the entity isn't stored, you can check if it is before with [`TransformBuffers:contains`].
pub fn update_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices {
let indices = self.not_updated.remove(&uuid)
.or_else(|| self.just_updated.remove(&uuid))
.expect("Use 'insert_entity' for new entities"); .expect("Use 'insert_entity' for new entities");
self.just_updated.insert(entity, indices); self.just_updated.insert(uuid, indices);
let normal_matrix = glam::Mat4::from_mat3(normal_matrix); let normal_matrix = glam::Mat4::from_mat3(normal_matrix);
@ -94,8 +106,8 @@ impl TransformBuffers {
indices indices
} }
/// Insert a new entity into the buffer, returns where it was stored. /// Insert a new transform into the buffer, returns where in the buffer it was stored.
pub fn insert_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: Entity, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { pub fn insert_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices {
let indices = match self.dead_indices.pop_front() { let indices = match self.dead_indices.pop_front() {
Some(indices) => indices, Some(indices) => indices,
None => { None => {
@ -113,28 +125,28 @@ impl TransformBuffers {
} }
}; };
self.just_updated.insert(entity, indices); self.just_updated.insert(uuid, indices);
self.update_entity(queue, limits, entity, transform, normal_matrix) self.update_transform(queue, limits, uuid, transform, normal_matrix)
} }
/// Update or insert an entities transform /// Update or insert a transform
pub fn update_or_insert<TFn>(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: Entity, transform_fn: TFn) -> TransformBufferIndices pub fn update_or_insert<TFn>(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform_fn: TFn) -> TransformBufferIndices
where TFn: Fn() -> (glam::Mat4, glam::Mat3) where TFn: Fn() -> (glam::Mat4, glam::Mat3)
{ {
let (tran, norm) = transform_fn(); let (tran, norm) = transform_fn();
if self.contains(entity) { if self.contains(uuid) {
self.update_entity(queue, limits, entity, tran, norm) self.update_transform(queue, limits, uuid, tran, norm)
} else { } else {
self.insert_entity(queue, limits, entity, tran, norm) self.insert_transform(queue, limits, uuid, tran, norm)
} }
} }
/// Returns true if the entity's transform is stored (does not mean its up-to-date). /// Returns true if the transform related to the `uuid` is stored (does not mean its up-to-date).
pub fn contains(&self, entity: Entity) -> bool { pub fn contains(&self, uuid: Uuid) -> bool {
self.not_updated.contains_key(&entity) || self.just_updated.contains_key(&entity) self.not_updated.contains_key(&uuid) || self.just_updated.contains_key(&uuid)
} }
/// Collect the dead entities, mark entities and not updated for next updates. /// Collect the dead transforms and prepare self to check next time.
pub fn tick(&mut self) { pub fn tick(&mut self) {
// take the dead entities, these were ones that were not updated this tick // take the dead entities, these were ones that were not updated this tick
let dead: VecDeque<TransformBufferIndices> = self.not_updated.values().copied().collect(); let dead: VecDeque<TransformBufferIndices> = self.not_updated.values().copied().collect();
@ -164,7 +176,10 @@ impl TransformBuffers {
.map(|entry| &entry.bindgroup) .map(|entry| &entry.bindgroup)
} }
/// Expand the Transform buffers by adding another uniform buffer binding /// Expand the Transform buffers by adding another uniform buffer binding.
///
/// This object has a chain of uniform buffers, when the buffers are expanded, a new
/// "chain-link" is created.
pub fn expand_buffers(&mut self, device: &wgpu::Device) { pub fn expand_buffers(&mut self, device: &wgpu::Device) {
let limits = device.limits(); let limits = device.limits();
let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2; let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2;
@ -228,7 +243,8 @@ impl TransformBuffers {
self.buffer_bindgroups.push(entry); self.buffer_bindgroups.push(entry);
} }
pub fn entity_indices(&self, entity: Entity) -> Option<&TransformBufferIndices> { /// Returns the indices of the Transform
self.just_updated.get(&entity).or_else(|| self.not_updated.get(&entity)) pub fn transform_indices(&self, uuid: Uuid) -> Option<&TransformBufferIndices> {
self.just_updated.get(&uuid).or_else(|| self.not_updated.get(&uuid))
} }
} }

View File

@ -1,15 +1,37 @@
use lyra_ecs::Component; use lyra_ecs::Component;
use lyra_resource::Mesh; use lyra_reflect::Reflect;
use lyra_resource::{gltf::Mesh, ResHandle};
#[derive(Clone, Component)] #[derive(Clone, Component, Reflect)]
pub struct MeshComponent { pub struct MeshComponent {
pub mesh: Mesh, #[reflect(skip)]
pub mesh: ResHandle<Mesh>,
}
impl From<ResHandle<Mesh>> for MeshComponent {
fn from(value: ResHandle<Mesh>) -> Self {
Self {
mesh: value
}
}
}
impl std::ops::Deref for MeshComponent {
type Target = ResHandle<Mesh>;
fn deref(&self) -> &Self::Target {
&self.mesh
}
}
impl std::ops::DerefMut for MeshComponent {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.mesh
}
} }
impl MeshComponent { impl MeshComponent {
pub fn new(mesh: Mesh) -> Self { pub fn new(mesh: ResHandle<Mesh>) -> Self {
Self { Self::from(mesh)
mesh,
}
} }
} }

View File

@ -1,9 +1,6 @@
pub mod mesh; pub mod mesh;
pub use mesh::*; pub use mesh::*;
pub mod model;
pub use model::*;
pub mod camera; pub mod camera;
pub use camera::*; pub use camera::*;

View File

@ -1,41 +0,0 @@
use lyra_ecs::Component;
use lyra_reflect::Reflect;
use lyra_resource::ResHandle;
use crate::assets::Model;
#[derive(Clone, Component, Reflect)]
pub struct ModelComponent(#[reflect(skip)] pub ResHandle<Model>);
impl From<ResHandle<Model>> for ModelComponent {
fn from(value: ResHandle<Model>) -> Self {
ModelComponent(value)
}
}
/* impl From<ResHandle<Model> for ModelComponent {
} */
impl std::ops::Deref for ModelComponent {
type Target = ResHandle<Model>;
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
}
}
} */

View File

@ -92,6 +92,13 @@ impl Transform {
trans.y += y; trans.y += y;
trans.z += z; trans.z += z;
} }
/// Combines a transform with another one.
///
/// The translations are added while the rotations and scales are multiplied.
pub fn combine(self, rhs: Transform) -> Transform {
self + rhs
}
/// Performs a linear interpolation between `self` and `rhs` based on the value `alpha`. /// Performs a linear interpolation between `self` and `rhs` based on the value `alpha`.
/// ///

View File

@ -6,7 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
lyra-ecs = { path = "../lyra-ecs" } lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-math = { path = "../lyra-math" }
anyhow = "1.0.75" anyhow = "1.0.75"
base64 = "0.21.4" base64 = "0.21.4"
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] } crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
@ -23,3 +24,4 @@ percent-encoding = "2.3.0"
thiserror = "1.0.48" thiserror = "1.0.48"
tracing = "0.1.37" tracing = "0.1.37"
uuid = { version = "1.4.1", features = ["v4"] } uuid = { version = "1.4.1", features = ["v4"] }
instant = "0.1"

View File

@ -1,8 +1,12 @@
use std::{sync::Arc, path::PathBuf}; use std::{sync::Arc, path::PathBuf};
use glam::{Quat, Vec3};
use instant::Instant;
use lyra_math::Transform;
use thiserror::Error; use thiserror::Error;
use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, ResHandle, Material, MeshIndices, ResourceManager, util}; use crate::{gltf::GltfScene, util, LoaderError, ResHandle, ResourceLoader, ResourceManager};
use super::{GltfNode, Material, Mesh, MeshIndices, MeshVertexAttribute, VertexAttributeData};
use tracing::debug; use tracing::debug;
@ -60,16 +64,23 @@ impl ModelLoader {
} }
} */ } */
fn process_node(buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> { fn process_node(ctx: &mut GltfLoadContext, materials: &Vec<ResHandle<Material>>, gnode: gltf::Node<'_>) -> GltfNode {
let mut meshes = vec![]; let mut node = GltfNode::default();
//node.transform()
node.transform = {
let gt = gnode.transform();
let (pos, rot, scale) = gt.decomposed();
Transform::new(Vec3::from(pos), Quat::from_array(rot), Vec3::from(scale))
};
node.name = gnode.name().map(str::to_string);
if let Some(mesh) = gnode.mesh() {
let mut new_mesh = Mesh::default();
if let Some(mesh) = node.mesh() {
for prim in mesh.primitives() { for prim in mesh.primitives() {
let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice())); let reader = prim.reader(|buf| Some(ctx.buffers[buf.index()].as_slice()));
let mut new_mesh = Mesh::default();
// read the positions // read the positions
if let Some(pos) = reader.read_positions() { if let Some(pos) = reader.read_positions() {
if prim.mode() != gltf::mesh::Mode::Triangles { if prim.mode() != gltf::mesh::Mode::Triangles {
@ -111,18 +122,20 @@ impl ModelLoader {
} }
let mat = materials.get(prim.material().index().unwrap()).unwrap(); let mat = materials.get(prim.material().index().unwrap()).unwrap();
new_mesh.set_material(mat.clone()); new_mesh.material = Some(mat.clone());
meshes.push(new_mesh);
} }
let handle = ResHandle::with_data("", new_mesh);
ctx.resource_manager.store_uuid(handle.clone());
node.mesh = Some(handle);
} }
for child in node.children() { for child in gnode.children() {
let mut child_meshes = ModelLoader::process_node(buffers, materials, child); let cmesh = ModelLoader::process_node(ctx, materials, child);
meshes.append(&mut child_meshes); node.children.push(cmesh);
} }
meshes node
} }
} }
@ -147,18 +160,6 @@ impl ResourceLoader for ModelLoader {
parent_path.pop(); parent_path.pop();
let parent_path = parent_path.display().to_string(); let parent_path = parent_path.display().to_string();
/* let (document, buffers, images) = gltf::import(&path)?;
let buffers: Vec<Vec<u8>> = buffers.into_iter().map(|b| b.0).collect();
let scene = document.scenes().next().unwrap();
let materials: Vec<Material> = document.materials()
.map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect();
let meshes: Vec<Mesh> = scene.nodes()
.map(|node| self.process_node(&buffers, &materials, node))
.flatten().collect(); */
let gltf = gltf::Gltf::open(path)?; let gltf = gltf::Gltf::open(path)?;
let mut use_bin = false; let mut use_bin = false;
@ -172,8 +173,7 @@ impl ResourceLoader for ModelLoader {
.map_err(ModelLoaderError::UriDecodingError), .map_err(ModelLoaderError::UriDecodingError),
}).collect(); }).collect();
// TODO: Read in multiple scenes let mut gltf_out = super::Gltf::default();
let scene = gltf.scenes().next().unwrap();
let mut context = GltfLoadContext { let mut context = GltfLoadContext {
resource_manager, resource_manager,
@ -183,15 +183,38 @@ impl ResourceLoader for ModelLoader {
buffers: &buffers, buffers: &buffers,
}; };
let materials: Vec<Material> = gltf.materials() let start_inst = Instant::now();
.map(|mat| Material::from_gltf(&mut context, mat)).collect(); let materials: Vec<ResHandle<Material>> = gltf.materials()
.map(|mat| ResHandle::with_data("", Material::from_gltf(&mut context, mat)))
let meshes: Vec<Mesh> = scene.nodes()
.flat_map(|node| ModelLoader::process_node(&buffers, &materials, node))
.collect(); .collect();
debug!("Loaded {} meshes, and {} materials from '{}'", meshes.len(), materials.len(), path); let mat_time = Instant::now() - start_inst;
debug!("Loaded {} materials in {}s", materials.len(), mat_time.as_secs_f32());
Ok(Arc::new(ResHandle::with_data(path, Model::new(meshes)))) for (idx, scene) in gltf.scenes().enumerate() {
let start_inst = Instant::now();
let nodes: Vec<GltfNode> = scene.nodes()
.map(|node| ModelLoader::process_node(&mut context, &materials, node))
.collect();
let node_time = Instant::now() - start_inst;
debug!("Loaded {} nodes in the scene in {}s", nodes.len(), node_time.as_secs_f32());
for mesh in nodes.iter().map(|n| &n.mesh) {
if let Some(mesh) = mesh {
gltf_out.meshes.push(mesh.clone());
}
}
let scene = GltfScene {
nodes,
};
let scene = ResHandle::with_data(&format!("{}:Scene{}", path, idx), scene);
gltf_out.scenes.push(scene);
}
gltf_out.materials = materials;
Ok(Arc::new(ResHandle::with_data(path, gltf_out)))
} }
#[allow(unused_variables)] #[allow(unused_variables)]
@ -202,7 +225,7 @@ impl ResourceLoader for ModelLoader {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::ResourceLoader; use crate::{gltf::Gltf, ResourceLoader};
use super::*; use super::*;
fn test_file_path(path: &str) -> String { fn test_file_path(path: &str) -> String {
@ -217,16 +240,27 @@ mod tests {
let mut manager = ResourceManager::new(); let mut manager = ResourceManager::new();
let loader = ModelLoader::default(); let loader = ModelLoader::default();
let model = loader.load(&mut manager, &path).unwrap(); let gltf = loader.load(&mut manager, &path).unwrap();
let model = Arc::downcast::<ResHandle<Model>>(model.as_arc_any()).unwrap(); let gltf = Arc::downcast::<ResHandle<Gltf>>(gltf.as_arc_any()).unwrap();
let model = model.data_ref(); let gltf = gltf.data_ref().unwrap();
assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh
let mesh = &model.meshes[0]; assert_eq!(gltf.scenes.len(), 1);
let scene = &gltf.scenes[0]
.data_ref().unwrap();
assert_eq!(scene.nodes.len(), 1);
let mnode = &scene.nodes[0];
assert!(mnode.mesh.is_some());
assert_eq!(mnode.transform, Transform::from_xyz(0.0, 0.0, 0.0));
assert_eq!(mnode.children.len(), 0);
let mesh = mnode.mesh.as_ref().unwrap();
let mesh = mesh.data_ref().unwrap();
assert!(mesh.position().unwrap().len() > 0); assert!(mesh.position().unwrap().len() > 0);
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()); assert!(mesh.material.as_ref().unwrap().data_ref().unwrap().base_color_texture.is_some());
let _mesh_mat = mesh.material(); // inner panic if material was not loaded
} }
} }

View File

@ -1,6 +1,9 @@
use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}};
use crate::{Texture, ResHandle, util, loader::model::GltfLoadContext}; use gltf::texture::{MagFilter, MinFilter};
use crate::{util, FilterMode, Image, ResHandle, Texture, TextureSampler, WrappingMode};
use super::loader::GltfLoadContext;
/// PBR metallic roughness /// PBR metallic roughness
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@ -247,15 +250,33 @@ impl Material {
fn load_texture(context: &mut GltfLoadContext, texture_info: gltf::texture::Info<'_>) -> ResHandle<Texture> { fn load_texture(context: &mut GltfLoadContext, texture_info: gltf::texture::Info<'_>) -> ResHandle<Texture> {
// TODO: texture_info.tex_coord() // TODO: texture_info.tex_coord()
let tex = texture_info.texture(); let tex = texture_info.texture();
// TODO: tex.sampler()
let img = tex.source(); let img = tex.source();
let src = img.source(); let src = img.source();
let buf = Material::read_source(context, src).unwrap(); let buf = Material::read_source(context, src).unwrap();
let buflen = buf.len(); let buflen = buf.len();
let mime_type = infer::get(&buf).expect("Failed to get file type").mime_type(); let mime_type = infer::get(&buf).expect("Failed to get image type").mime_type();
context.resource_manager.load_bytes::<Texture>(&uuid::Uuid::new_v4().to_string(), mime_type, let tex_img = context.resource_manager.load_bytes::<Image>(&uuid::Uuid::new_v4().to_string(), mime_type,
buf, 0, buflen).unwrap() buf, 0, buflen).unwrap();
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(),
wrap_w: WrappingMode::ClampToEdge,
};
let handler = ResHandle::with_data("", Texture {
image: tex_img,
sampler: Some(samp),
});
context.resource_manager.store_uuid(handler.clone());
handler
} }
/// Load the Material from a gltf::Material. /// Load the Material from a gltf::Material.
@ -295,4 +316,55 @@ impl Material {
specular, specular,
} }
} }
}
impl From<gltf::texture::MagFilter> for FilterMode {
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
pub fn from_min_filter(minf: MinFilter) -> FilterMode {
match minf {
MinFilter::Nearest => FilterMode::Nearest,
MinFilter::Linear => FilterMode::Linear,
MinFilter::NearestMipmapNearest => FilterMode::Nearest,
MinFilter::LinearMipmapNearest => FilterMode::Linear,
MinFilter::NearestMipmapLinear => FilterMode::Nearest,
MinFilter::LinearMipmapLinear => FilterMode::Linear,
}
}
pub fn from_mag_filter(magf: MagFilter) -> FilterMode {
match magf {
MagFilter::Nearest => FilterMode::Nearest,
MagFilter::Linear => FilterMode::Linear,
}
}
pub fn mipmap_filter(minf: MinFilter) -> Option<FilterMode> {
match minf {
MinFilter::Nearest => None,
MinFilter::Linear => None,
MinFilter::NearestMipmapNearest => Some(FilterMode::Nearest),
MinFilter::LinearMipmapNearest => Some(FilterMode::Nearest),
MinFilter::NearestMipmapLinear => Some(FilterMode::Linear),
MinFilter::LinearMipmapLinear => Some(FilterMode::Linear),
}
}
}
impl From<gltf::texture::WrappingMode> for WrappingMode {
fn from(value: gltf::texture::WrappingMode) -> Self {
match value {
gltf::texture::WrappingMode::ClampToEdge => Self::ClampToEdge,
gltf::texture::WrappingMode::MirroredRepeat => Self::MirroredRepeat,
gltf::texture::WrappingMode::Repeat => Self::Repeat,
}
}
} }

View File

@ -1,7 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::Material; use crate::{lyra_engine, ResHandle};
use crate::lyra_engine;
use super::Material;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -82,23 +83,11 @@ pub enum MeshVertexAttribute {
Other(String), Other(String),
} }
#[derive(Clone, lyra_ecs::Component)] #[derive(Clone, Default, lyra_ecs::Component)]
pub struct Mesh { pub struct Mesh {
pub uuid: uuid::Uuid,
pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>, pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>,
pub indices: Option<MeshIndices>, pub indices: Option<MeshIndices>,
material: Option<Material>, pub material: Option<ResHandle<Material>>,
}
impl Default for Mesh {
fn default() -> Self {
Self {
uuid: uuid::Uuid::new_v4(),
attributes: Default::default(),
indices: Default::default(),
material: Default::default()
}
}
} }
impl Mesh { impl Mesh {
@ -127,26 +116,4 @@ impl Mesh {
self.attributes.get(&MeshVertexAttribute::TexCoords) self.attributes.get(&MeshVertexAttribute::TexCoords)
.map(|p| p.as_vec2()) .map(|p| p.as_vec2())
} }
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)]
pub struct Model {
pub meshes: Vec<Mesh>,
//pub material
}
impl Model {
pub fn new(meshes: Vec<Mesh>) -> Self {
Self {
meshes,
}
}
} }

View File

@ -0,0 +1,37 @@
pub mod loader;
pub use loader::*;
pub mod material;
use lyra_math::Transform;
pub use material::*;
pub mod mesh;
pub use mesh::*;
pub mod scene;
pub use scene::*;
use crate::ResHandle;
/// A loaded Gltf file
#[derive(Clone, Default)]
pub struct Gltf {
pub scenes: Vec<ResHandle<GltfScene>>,
pub materials: Vec<ResHandle<Material>>,
pub meshes: Vec<ResHandle<Mesh>>,
}
impl Gltf {
/// Collects all Gltf meshes and gets their world Transform.
pub fn collect_world_meshes(&self) -> Vec<(ResHandle<Mesh>, Transform)> {
let mut v = vec![];
for scene in self.scenes.iter() {
let mut tmp = scene.data_ref()
.unwrap().collect_world_meshes();
v.append(&mut tmp);
}
v
}
}

View File

@ -0,0 +1,55 @@
use lyra_math::Transform;
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>,
}
/// A Scene in a Gltf file
#[derive(Clone)]
pub struct GltfScene {
pub nodes: Vec<GltfNode>,
}
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
}
}

View File

@ -10,11 +10,7 @@ pub use texture::*;
pub mod loader; pub mod loader;
pub use loader::*; pub use loader::*;
pub mod model; pub mod gltf;
pub use model::*;
pub mod material;
pub use material::*;
pub mod world_ext; pub mod world_ext;
pub use world_ext::*; pub use world_ext::*;

View File

@ -1,8 +1,9 @@
use std::{fs::File, sync::Arc, io::Read}; use std::{fs::File, sync::Arc, io::Read};
use image::ImageError; use image::ImageError;
use tracing::{debug, trace};
use crate::{resource_manager::ResourceStorage, texture::Texture, resource::ResHandle, ResourceManager}; use crate::{resource::ResHandle, resource_manager::ResourceStorage, Image, ResourceManager};
use super::{LoaderError, ResourceLoader}; use super::{LoaderError, ResourceLoader};
@ -12,7 +13,7 @@ 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 images.
#[derive(Default)] #[derive(Default)]
pub struct ImageLoader; pub struct ImageLoader;
@ -59,25 +60,25 @@ impl ResourceLoader for ImageLoader {
ImageError::IoError(e) => LoaderError::IoError(e), ImageError::IoError(e) => LoaderError::IoError(e),
_ => LoaderError::DecodingError(e.into()), _ => LoaderError::DecodingError(e.into()),
})?; })?;
let texture = Texture { let image = Image::from(image);
image, let res = ResHandle::with_data(path, image);
};
let res = ResHandle::with_data(path, texture);
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> { fn load_bytes(&self, _resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
trace!("Loading {} bytes as an image", length);
let image = image::load_from_memory(&bytes[offset..(length-offset)]) let image = image::load_from_memory(&bytes[offset..(length-offset)])
.map_err(|e| match e { .map_err(|e| match e {
ImageError::IoError(e) => LoaderError::IoError(e), ImageError::IoError(e) => LoaderError::IoError(e),
_ => LoaderError::DecodingError(e.into()), _ => LoaderError::DecodingError(e.into()),
})?; })?;
let texture = Texture { let image = Image::from(image);
image, let res = ResHandle::with_data(&uuid::Uuid::new_v4().to_string(), image);
};
let res = ResHandle::with_data(&uuid::Uuid::new_v4().to_string(), texture);
debug!("Finished loading image of {} bytes", length);
Ok(Arc::new(res)) Ok(Arc::new(res))
} }
} }

View File

@ -1,5 +1,4 @@
pub mod image; pub mod image;
pub mod model;
use std::{io, sync::Arc, path::Path, ffi::OsStr}; use std::{io, sync::Arc, path::Path, ffi::OsStr};

View File

@ -1,5 +1,7 @@
use std::{any::Any, sync::{Arc, RwLock}}; use std::{any::Any, sync::{Arc, RwLock}};
use lyra_ecs::Component;
use crate::lyra_engine;
use uuid::Uuid; use uuid::Uuid;
use crate::ResourceStorage; use crate::ResourceStorage;
@ -38,7 +40,8 @@ pub struct Resource<T> {
/// This struct has an inner [`RwLock`] to the resource data, so most methods may be blocking. /// This struct has an inner [`RwLock`] to the resource data, so most methods may be blocking.
/// However, the only times it will be blocking is if another thread is reloading the resource /// However, the only times it will be blocking is if another thread is reloading the resource
/// and has a write lock on the data. This means that most of the time, it is not blocking. /// and has a write lock on the data. This means that most of the time, it is not blocking.
pub struct ResHandle<T> { #[derive(Component)]
pub struct ResHandle<T: 'static> {
pub(crate) data: Arc<RwLock<Resource<T>>>, pub(crate) data: Arc<RwLock<Resource<T>>>,
} }
@ -103,18 +106,7 @@ impl<T> ResHandle<T> {
} }
/// Get a reference to the data in the resource /// Get a reference to the data in the resource
/// pub fn data_ref<'a>(&'a self) -> Option<ResourceDataRef<'a, T>> {
/// # Panics
/// Panics if the resource was not loaded yet.
pub fn data_ref<'a>(&'a self) -> ResourceDataRef<'a, T> {
let d = self.data.read().expect("Resource mutex was poisoned!");
ResourceDataRef {
guard: d
}
}
/// Attempt to get a borrow to the resource data. Returns `None` if the resource is not loaded.
pub fn try_data_ref<'a>(&'a self) -> Option<ResourceDataRef<'a, T>> {
if self.is_loaded() { if self.is_loaded() {
let d = self.data.read().expect("Resource mutex was poisoned!"); let d = self.data.read().expect("Resource mutex was poisoned!");
Some(ResourceDataRef { Some(ResourceDataRef {

View File

@ -6,7 +6,7 @@ use notify_debouncer_full::{DebouncedEvent, FileIdMap};
use thiserror::Error; use thiserror::Error;
use uuid::Uuid; use uuid::Uuid;
use crate::{loader::{image::ImageLoader, model::ModelLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState}; use crate::{gltf::ModelLoader, loader::{image::ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState};
/// A trait for type erased storage of a resource. /// A trait for type erased storage of a resource.
/// Implemented for [`ResHandle<T>`] /// Implemented for [`ResHandle<T>`]
@ -56,6 +56,7 @@ pub struct ResourceWatcher {
pub struct ResourceManager { pub struct ResourceManager {
resources: HashMap<String, Arc<dyn ResourceStorage>>, resources: HashMap<String, Arc<dyn ResourceStorage>>,
uuid_resources: HashMap<Uuid, Arc<dyn ResourceStorage>>,
loaders: Vec<Arc<dyn ResourceLoader>>, loaders: Vec<Arc<dyn ResourceLoader>>,
watchers: HashMap<String, ResourceWatcher>, watchers: HashMap<String, ResourceWatcher>,
} }
@ -70,6 +71,7 @@ impl ResourceManager {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
resources: HashMap::new(), resources: HashMap::new(),
uuid_resources: HashMap::new(),
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ], loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
watchers: HashMap::new(), watchers: HashMap::new(),
} }
@ -148,6 +150,29 @@ impl ResourceManager {
} }
} }
/// Store a resource using its uuid.
///
/// The resource cannot be requested with [`ResourceManager::request`], it can only be
/// retrieved with [`ResourceManager::request_uuid`].
pub fn store_uuid<T: Send + Sync + 'static>(&mut self, res: ResHandle<T>) {
self.uuid_resources.insert(res.uuid(), Arc::new(res));
}
/// Request a resource via its uuid.
///
/// Returns `None` if the resource was not found. The resource must of had been
/// stored with [`ResourceManager::request`] to return `Some`.
pub fn request_uuid<T: Send + Sync + 'static>(&mut self, uuid: &Uuid) -> Option<ResHandle<T>> {
match self.uuid_resources.get(uuid) {
Some(res) => {
let res = res.clone().as_arc_any();
let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
Some(ResHandle::<T>::clone(&res))
},
None => None,
}
}
/// Store bytes in the manager. If there is already an entry with the same identifier it will be updated. /// 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. /// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic.
@ -304,7 +329,7 @@ mod tests {
let mut man = ResourceManager::new(); let mut man = ResourceManager::new();
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap(); let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
assert_eq!(res.state(), ResourceState::Ready); assert_eq!(res.state(), ResourceState::Ready);
let img = res.try_data_ref(); let img = res.data_ref();
img.unwrap(); img.unwrap();
} }
@ -340,7 +365,7 @@ mod tests {
let mut man = ResourceManager::new(); let mut man = ResourceManager::new();
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap(); let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
assert_eq!(res.state(), ResourceState::Ready); assert_eq!(res.state(), ResourceState::Ready);
let img = res.try_data_ref(); let img = res.data_ref();
img.unwrap(); img.unwrap();
println!("Path = {}", res.path()); println!("Path = {}", res.path());
@ -360,7 +385,7 @@ mod tests {
let mut man = ResourceManager::new(); let mut man = ResourceManager::new();
let res = man.request::<Texture>(&image_path).unwrap(); let res = man.request::<Texture>(&image_path).unwrap();
assert_eq!(res.state(), ResourceState::Ready); assert_eq!(res.state(), ResourceState::Ready);
let img = res.try_data_ref(); let img = res.data_ref();
img.unwrap(); img.unwrap();
let recv = man.watch(&image_path, false).unwrap(); let recv = man.watch(&image_path, false).unwrap();

View File

@ -1,10 +1,73 @@
use std::ops::{Deref, DerefMut};
//pub use gltf::texture::{MagFilter, MinFilter, WrappingMode};
use image::DynamicImage; use image::DynamicImage;
use crate::ResHandle;
/// The filter mode of the sampler.
///
/// This is used for minification, magnification, and mipmap filters
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum FilterMode {
Nearest,
Linear,
}
/// The wrapping mode of the Texture coordinates
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum WrappingMode {
ClampToEdge,
MirroredRepeat,
Repeat,
}
/// The descriptor of the sampler for a Texture.
#[derive(Clone)]
pub struct TextureSampler {
pub mag_filter: Option<FilterMode>,
pub min_filter: Option<FilterMode>,
pub mipmap_filter: Option<FilterMode>,
pub wrap_u: WrappingMode,
pub wrap_v: WrappingMode,
pub wrap_w: WrappingMode,
}
#[derive(Clone)]
pub struct Image(DynamicImage);
impl Deref for Image {
type Target = DynamicImage;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Image {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<DynamicImage> for Image {
fn from(value: DynamicImage) -> Self {
Self(value)
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct Texture { pub struct Texture {
pub image: DynamicImage, pub image: ResHandle<Image>,
pub sampler: Option<TextureSampler>,
} }
impl Texture { impl Texture {
/// Create a texture from an image.
pub fn from_image(image: ResHandle<Image>) -> Self {
Self {
image,
sampler: None,
}
}
} }

View File

@ -51,7 +51,7 @@ impl<'a, T> DerefMut for MutCow<'a, T> {
/// This SceneGraph is special in the sense that it is literally just an ECS world with methods /// This SceneGraph is special in the sense that it is literally just an ECS world with methods
/// implemented for it that make it easier to use for a SceneGraph. /// implemented for it that make it easier to use for a SceneGraph.
//#[derive(Default)] //#[derive(Default)]
pub struct SceneGraph<'a> { pub struct SceneGraph<'a> {
pub(crate) world: MutCow<'a, World>, pub(crate) world: MutCow<'a, World>,
root_node: SceneNode, root_node: SceneNode,
} }
@ -162,6 +162,13 @@ impl<'a> SceneGraph<'a> {
world_add_child_node(&mut self.world, parent, local_transform, bundle) world_add_child_node(&mut self.world, parent, local_transform, bundle)
} }
pub fn add_empty_node_under(&mut self, parent: &SceneNode, local_transform: Transform) -> SceneNode {
let e = self.world.spawn((SceneNodeFlag, local_transform));
self.world.add_relation(e, ChildOf, parent.entity());
SceneNode::new(Some(parent.entity()), e)
}
/// Traverses down the SceneGraph, calling `callback` with each SceneNode and its world transform. /// Traverses down the SceneGraph, calling `callback` with each SceneNode and its world transform.
/// ///
/// The traversal does not include the root scene node. /// The traversal does not include the root scene node.
@ -172,7 +179,7 @@ impl<'a> SceneGraph<'a> {
self.traverse_down_from(self.root_node.clone(), &mut callback); self.traverse_down_from(self.root_node.clone(), &mut callback);
} }
/// Traverses down the SceneGraph from a starting node, calling `callback` with each /// Recursively Traverses down the SceneGraph from a starting node, calling `callback` with each
/// SceneNode and its world transform. /// SceneNode and its world transform.
fn traverse_down_from<F>(&self, start: SceneNode, callback: &mut F) fn traverse_down_from<F>(&self, start: SceneNode, callback: &mut F)
where where
@ -189,6 +196,10 @@ impl<'a> SceneGraph<'a> {
self.traverse_down_from(node, callback); self.traverse_down_from(node, callback);
} }
} }
pub fn root_node(&self) -> SceneNode {
self.root_node.clone()
}
} }
/// Add a node under a parent node. /// Add a node under a parent node.
@ -256,7 +267,7 @@ pub mod tests {
}); });
} }
#[test] /* #[test]
fn inserting_and_from_world() { fn inserting_and_from_world() {
let v2s = vec![Vec3::new(10.0, 10.0, 10.0), Vec3::new(50.0, 50.0, 50.0)]; let v2s = vec![Vec3::new(10.0, 10.0, 10.0), Vec3::new(50.0, 50.0, 50.0)];
@ -301,5 +312,5 @@ pub mod tests {
idx += 1; idx += 1;
}); });
} } */
} }