Compare commits

..

5 Commits

53 changed files with 1477 additions and 680 deletions

18
.vscode/launch.json vendored
View File

@ -4,6 +4,24 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug lyra dim_2d example",
"cargo": {
"args": [
"build",
"--manifest-path", "${workspaceFolder}/examples/2d/Cargo.toml"
//"--bin=testbed",
],
"filter": {
"name": "dim_2d",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}/examples/2d"
},
{
"type": "lldb",
"request": "launch",

View File

@ -1,5 +1,5 @@
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use syn::{parse_macro_input, spanned::Spanned, DeriveInput};
#[proc_macro_derive(Component)]
pub fn derive_component(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
@ -17,3 +17,53 @@ pub fn derive_component(input: proc_macro::TokenStream) -> proc_macro::TokenStre
}
})
}
#[proc_macro_derive(Bundle)]
pub fn derive_bundle(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let type_ident = &input.ident;
let s = match &input.data {
syn::Data::Struct(s) => {
s
},
_ => {
return syn::Error::new(input.span(), "Bundle derive macro only supports Structs")
.into_compile_error().into();
}
};
let field_names: Vec<_> = s.fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
proc_macro::TokenStream::from(quote! {
impl #impl_generics lyra_engine::ecs::Bundle for #type_ident #ty_generics #where_clause {
fn type_ids(&self) -> Vec<lyra_engine::ecs::DynTypeId> {
let mut v = vec![];
#(
v.extend(self.#field_names.type_ids().into_iter());
)*
v
}
fn info(&self) -> Vec<lyra_engine::ecs::ComponentInfo> {
let mut v = vec![];
#(
v.extend(self.#field_names.info().into_iter());
)*
v
}
fn take(self, f: &mut impl FnMut(std::ptr::NonNull<u8>, lyra_engine::ecs::DynTypeId, lyra_engine::ecs::ComponentInfo)) {
#(
self.#field_names.take(f);
)*
}
fn is_dynamic(&self) -> bool {
false
}
}
})
}

View File

@ -348,7 +348,7 @@ impl Archetype {
self.entity_ids.insert(entity, entity_index);
self.entities.push(entity);
bundle.take(|data, type_id, info| {
bundle.take(&mut |data, type_id, info| {
self.put_component_at(
tick,
data,
@ -621,7 +621,7 @@ impl Archetype {
}
for (eid, bundle) in new_columns.into_iter().enumerate() {
bundle.take(|ptr, tyid, _size| unsafe {
bundle.take(&mut |ptr, tyid, _size| unsafe {
let col = self.get_column_mut(tyid).unwrap();
col.insert_entity(eid, ptr, tick.clone());
});

View File

@ -11,7 +11,7 @@ pub trait Bundle {
/// Take the bundle by calling the closure with pointers to each component, its type and size.
/// The closure is expected to take ownership of the pointer.
fn take(self, f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo));
fn take(self, f: &mut impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo));
/// Returns a boolean indicating if this Bundle is dynamic. See [`DynamicBundle`]
fn is_dynamic(&self) -> bool;
@ -26,7 +26,7 @@ impl Bundle for () {
vec![ComponentInfo::new::<()>()]
}
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
fn take(self, f: &mut impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
f(NonNull::from(&self).cast(), DynTypeId::of::<()>(), ComponentInfo::new::<()>());
}
@ -44,7 +44,7 @@ impl<C: Component> Bundle for C {
vec![ComponentInfo::new::<C>()]
}
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
fn take(self, f: &mut impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
f(NonNull::from(&self).cast(), DynTypeId::of::<C>(), ComponentInfo::new::<C>());
// this must be done to avoid calling drop on heap memory that the component
@ -59,29 +59,41 @@ impl<C: Component> Bundle for C {
macro_rules! impl_bundle_tuple {
( $($name: ident),+ ) => (
// these names wont follow rust convention, but its a macro so deal with it
#[allow(non_snake_case)]
impl<$($name: Component),+> Bundle for ($($name,)+) {
impl<$($name: Bundle),+> Bundle for ($($name,)+) {
#[inline(always)]
fn type_ids(&self) -> Vec<DynTypeId> {
// these names wont follow rust convention, but its a macro so deal with it
vec![$(DynTypeId::of::<$name>()),+]
let ($($name,)+) = self;
let mut v = vec![];
$(
v.extend($name.type_ids().into_iter());
)+
v
}
#[inline(always)]
fn info(&self) -> Vec<ComponentInfo> {
vec![$(ComponentInfo::new::<$name>()),+]
let ($($name,)+) = self;
let mut v = vec![];
$(
v.extend($name.info().into_iter());
)+
v
}
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
// these names wont follow rust convention, but its a macro so deal with it
#[inline(always)]
fn take(self, f: &mut impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
let ($($name,)+) = self;
$(
f(NonNull::from(&$name).cast(), DynTypeId::of::<$name>(), ComponentInfo::new::<$name>());
// this must be done to avoid calling drop on heap memory that the component
// may manage. So something like a Vec, or HashMap, etc.
std::mem::forget($name);
$name.take(f);
)+
}
#[inline(always)]
fn is_dynamic(&self) -> bool {
false
}
@ -166,7 +178,7 @@ impl DynamicBundle {
where
B: Bundle
{
bundle.take(|ptr, _, info| {
bundle.take(&mut |ptr, _, info| {
// unfortunately the components in the bundle must be copied since there is no guarantee that
// `bundle` lasts for as long as the `DynamicBundle`. If the data wasn't copied, the pointers
// could be invalid later.
@ -192,7 +204,7 @@ impl Bundle for DynamicBundle {
self.bundle.iter().map(|b| b.1).collect()
}
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
fn take(self, f: &mut impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
for (data, info) in self.bundle.into_iter() {
f(data, info.type_id(), info);
}
@ -202,3 +214,70 @@ impl Bundle for DynamicBundle {
true
}
}
#[cfg(test)]
mod tests {
use lyra_ecs_derive::{Bundle, Component};
use crate::{lyra_engine, ComponentInfo, World};
use super::Bundle;
#[allow(dead_code)]
#[derive(Component, PartialEq, Clone, Copy, Debug)]
struct Vec2 {
x: f32,
y: f32,
}
#[allow(dead_code)]
#[derive(Component, Debug, PartialEq, Eq, Clone, Copy)]
enum SomeFlag {
SomethingA,
SomethingB,
}
#[derive(Bundle)]
struct CompBundle {
pos: Vec2,
flag: SomeFlag
}
#[test]
fn check_bundle_order() {
let b = CompBundle {
pos: Vec2 {
x: 10.0, y: 10.0,
},
flag: SomeFlag::SomethingA,
};
let info = b.info();
let mut info = info.into_iter();
assert_eq!(info.next().unwrap(), ComponentInfo::new::<Vec2>());
assert_eq!(info.next().unwrap(), ComponentInfo::new::<SomeFlag>());
}
#[test]
fn check_bundle_spawn() {
let b_pos = Vec2 {
x: 10.0, y: 10.0,
};
let b_flag = SomeFlag::SomethingA;
let b = CompBundle {
pos: b_pos,
flag: b_flag,
};
let mut world = World::new();
let e = world.spawn(b);
let pos = world.view_one::<&Vec2>(e).get()
.expect("failed to find spawned Vec2 from Bundle on Entity");
assert!(pos.x == b_pos.x && pos.y == b_pos.y, "Spawned Vec2 values were not correct, got: {:?}, expected: {:?}", *pos, b_pos);
let flag = world.view_one::<&SomeFlag>(e).get()
.expect("failed to find spawned SomeFlag from Bundle on Entity");
assert_eq!(*flag, b_flag);
}
}

View File

@ -1,6 +1,4 @@
mod has;
use std::marker::PhantomData;
pub use has::*;
mod or;

View File

@ -112,7 +112,7 @@ impl<'a, T: ResourceObject> Res<'a, T> {
/// Returns a boolean indicating if the resource changed.
pub fn changed(&self) -> bool {
*self.inner.tick >= *self.world_tick - 1
self.inner.changed(self.world_tick)
}
/// The tick that this resource was last modified at
@ -236,7 +236,7 @@ impl<'a, T: ResourceObject> ResMut<'a, T> {
}
pub fn changed(&self) -> bool {
*self.inner.tick - 1 >= *self.world_tick - 1
self.inner.changed(self.world_tick)
}
/// The tick that this resource was last modified at

View File

@ -2,7 +2,7 @@ use std::{any::{Any, TypeId}, sync::Arc};
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use crate::{Tick, TickTracker};
use crate::Tick;
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
pub trait ResourceObject: Send + Sync + Any {
@ -25,6 +25,14 @@ pub struct TrackedResource<T: ?Sized> {
pub res: T,
}
impl<T: ?Sized> TrackedResource<T> {
pub fn changed(&self, tick: Tick) -> bool {
let tick = tick.checked_sub(1).unwrap_or(0);
//println!("self: {}, world: {}", *self.tick, tick);
*self.tick >= tick
}
}
/// A type erased storage for a Resource.
#[derive(Clone)]
pub struct ResourceData {
@ -89,6 +97,6 @@ impl ResourceData {
}
pub fn changed(&self, tick: Tick) -> bool {
*self.data.borrow().tick >= *tick - 1
self.data.borrow().changed(tick)
}
}

View File

@ -180,7 +180,7 @@ impl World {
let entry_idx = *current_arch.entity_indexes()
.get(&entity).unwrap();
bundle.take(|ptr, id, _info| {
bundle.take(&mut |ptr, id, _info| {
let col = current_arch.get_column_mut(id).unwrap();
unsafe { col.set_at(entry_idx.0 as _, ptr, tick) };
});
@ -459,6 +459,8 @@ impl World {
}
/// Gets a resource from the World.
///
/// Returns `None` if the resource wasn't found, or is already borrowed.
pub fn get_resource<T: ResourceObject>(&self) -> Option<Res<T>> {
self.get_tracked_resource::<T>().map(|r| Res {
inner: r,
@ -476,20 +478,24 @@ impl World {
/// Gets a reference to a change tracked resource.
///
/// Returns `None` if the resource wasn't found, or is already borrowed.
///
/// You will have to manually downcast the inner resource. Most people don't need this, see
/// [`World::get_resource`].
pub fn get_tracked_resource<T: ResourceObject>(&self) -> Option<AtomicRef<TrackedResource<dyn ResourceObject>>> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.data.borrow())
.and_then(|r| r.data.try_borrow().ok())
}
/// Gets a mutable borrow to a change tracked resource.
///
/// Returns `None` if the resource wasn't found, or is already borrowed.
///
/// You will have to manually downcast the inner resource. Most people don't need this, see
/// [`World::get_resource_mut`].
pub fn get_tracked_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<TrackedResource<dyn ResourceObject>>> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.data.borrow_mut())
.and_then(|r| r.data.try_borrow_mut().ok())
}
/// Returns a boolean indicating if the resource changed.
@ -791,14 +797,18 @@ mod tests {
world.add_resource(SimpleCounter(50));
assert!(world.has_resource_changed::<SimpleCounter>());
world.tick();
world.spawn(Vec2::new(50.0, 50.0));
world.tick();
assert!(!world.has_resource_changed::<SimpleCounter>());
world.tick();
let mut counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
counter.0 += 100;
drop(counter);
assert!(world.has_resource_changed::<SimpleCounter>());
}

View File

@ -4,6 +4,7 @@ use lyra_gltf::GltfLoader;
use lyra_resource::ResourceManager;
use crate::game::App;
use crate::render::PreprocessShaderLoader;
use crate::winit::{WinitPlugin, WindowPlugin};
use crate::DeltaTimePlugin;
use crate::input::InputPlugin;
@ -101,7 +102,10 @@ pub struct ResourceManagerPlugin;
impl Plugin for ResourceManagerPlugin {
fn setup(&mut self, app: &mut App) {
app.world.add_resource(ResourceManager::new());
let rm = ResourceManager::new();
rm.register_loader::<PreprocessShaderLoader>();
app.world.add_resource(rm);
}
}
@ -111,12 +115,12 @@ pub struct DefaultPlugins;
impl Plugin for DefaultPlugins {
fn setup(&mut self, app: &mut App) {
WinitPlugin::default().setup(app);
CommandQueuePlugin.setup(app);
InputPlugin.setup(app);
ResourceManagerPlugin.setup(app);
GltfPlugin.setup(app);
WinitPlugin::default().setup(app);
WindowPlugin::default().setup(app);
CommandQueuePlugin.setup(app);
InputPlugin.setup(app);
DeltaTimePlugin.setup(app);
}
}

View File

@ -1,40 +1,7 @@
use lyra_reflect::Reflect;
use winit::dpi::PhysicalSize;
use glam::Vec2;
use lyra_math::Transform;
use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX}, scene::CameraComponent};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub enum CameraProjectionMode {
/// 3d camera projection
Perspective,
/// 2d camera projection
Orthographic,
}
#[derive(Debug, Clone)]
pub struct Projection {
aspect: f32,
znear: f32,
zfar: f32,
}
impl Projection {
pub fn new(width: u32, height: u32, znear: f32, zfar: f32) -> Self {
Self {
aspect: width as f32 / height as f32,
znear,
zfar,
}
}
pub fn resize(&mut self, width: u32, height: u32) {
self.aspect = width as f32 / height as f32;
}
pub fn calc_matrix(&self, fov: Angle, _mode: CameraProjectionMode) -> glam::Mat4 {
OPENGL_TO_WGPU_MATRIX * glam::Mat4::perspective_rh_gl(fov.to_radians(), self.aspect, self.znear, self.zfar)
}
}
use crate::scene::CameraProjection;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
@ -48,9 +15,7 @@ pub struct CameraUniform {
pub projection: glam::Mat4,
/// The position of the camera
pub position: glam::Vec3,
pub tile_debug: u32,
//_padding: [u8; 3],
_padding: u32,
}
impl Default for CameraUniform {
@ -61,106 +26,45 @@ impl Default for CameraUniform {
view_projection: glam::Mat4::IDENTITY,
projection: glam::Mat4::IDENTITY,
position: Default::default(),
tile_debug: 0,
//_padding: 0,
_padding: 0,
}
}
}
impl CameraUniform {
pub fn new(view: glam::Mat4, inverse_projection: glam::Mat4, view_projection: glam::Mat4, projection: glam::Mat4, position: glam::Vec3) -> Self {
pub fn new(
view: glam::Mat4,
inverse_projection: glam::Mat4,
view_projection: glam::Mat4,
projection: glam::Mat4,
position: glam::Vec3,
) -> Self {
Self {
view,
inverse_projection,
view_projection,
projection,
position,
tile_debug: 0
_padding: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct RenderCamera {
view_proj: glam::Mat4,
pub fn from_component(transform: Transform, projection: CameraProjection, viewport_size: Vec2) -> Self {
let position = transform.translation;
let forward = transform.forward();
let up = transform.up();
let view = glam::Mat4::look_to_rh(position, forward, up);
#[allow(dead_code)]
size: PhysicalSize<u32>,
aspect: f32,
znear: f32,
zfar: f32,
}
let projection = projection.to_mat4(viewport_size);
let view_projection = projection * view;
impl RenderCamera {
pub fn new(size: PhysicalSize<u32>) -> Self {
Self {
view_proj: glam::Mat4::IDENTITY,
size,
aspect: size.width as f32 / size.height as f32,
znear: 0.1,
zfar: 100.0,
}
}
pub fn update_aspect_ratio(&mut self, size: PhysicalSize<u32>) {
self.aspect = size.width as f32 / size.height as f32;
}
/// Calculates the view projection, and the view
///
/// Returns: A tuple with the view projection as the first element, and the
/// view matrix as the second.
pub fn calc_view_projection(&mut self, camera: &CameraComponent) -> CameraUniform {
let position = camera.transform.translation;
let forward = camera.transform.forward();
let up = camera.transform.up();
let view = glam::Mat4::look_to_rh(
position,
forward,
up
);
match camera.mode {
CameraProjectionMode::Perspective => {
let proj = glam::Mat4::perspective_rh_gl(camera.fov.to_radians(), self.aspect, self.znear, self.zfar);
self.view_proj = OPENGL_TO_WGPU_MATRIX * proj * view;
//(&self.view_proj, view)
CameraUniform {
view,
inverse_projection: proj.inverse(),
view_projection: self.view_proj,
projection: proj,
inverse_projection: projection.inverse(),
view_projection,
projection,
position,
tile_debug: camera.debug as u32,
}
},
CameraProjectionMode::Orthographic => {
let target = camera.transform.rotation * glam::Vec3::new(0.0, 0.0, -1.0);
let target = target.normalize();
let ratio_size_per_depth = ((camera.fov.to_radians() / 2.0) * 2.0).atan();
let distance = (target - position).length();
let size_y = ratio_size_per_depth * distance;
let size_x = ratio_size_per_depth * distance * self.aspect;
let proj = glam::Mat4::orthographic_rh_gl(-size_x, size_x, -size_y, size_y, self.znear, self.zfar);
self.view_proj = OPENGL_TO_WGPU_MATRIX * proj;
CameraUniform {
view,
inverse_projection: proj.inverse(),
view_projection: self.view_proj,
projection: proj,
position,
tile_debug: camera.debug as u32,
}
},
_padding: 0,
}
}
}

View File

@ -4,6 +4,7 @@ use std::{
};
use lyra_ecs::World;
use lyra_resource::{RequestError, ResHandle, ResourceManager};
pub use node::*;
mod passes;
@ -22,7 +23,7 @@ use rustc_hash::FxHashMap;
use tracing::{debug_span, instrument, trace, warn};
use wgpu::CommandEncoder;
use super::resource::{ComputePipeline, Pass, Pipeline, RenderPipeline};
use super::{resource::{ComputePipeline, Pass, Pipeline, RenderPipeline}, Shader};
/// A trait that represents the label of a resource, slot, or node in the [`RenderGraph`].
pub trait RenderGraphLabel: Debug + 'static {
@ -118,11 +119,15 @@ pub struct RenderGraph {
/// A directed graph used to determine dependencies of nodes.
node_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
view_target: Rc<RefCell<ViewTarget>>,
shader_prepoc: wgsl_preprocessor::Processor,
/// Type erased data of ResourceManager ecs resource
resource_manager: lyra_ecs::ResourceData,
}
impl RenderGraph {
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, view_target: Rc<RefCell<ViewTarget>>) -> Self {
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, view_target: Rc<RefCell<ViewTarget>>, world: &World) -> Self {
let rm = world.get_resource_data::<ResourceManager>()
.expect("RenderGraph requires ResourceManager ECS resource");
Self {
device,
queue,
@ -132,7 +137,7 @@ impl RenderGraph {
bind_groups: Default::default(),
node_graph: Default::default(),
view_target,
shader_prepoc: wgsl_preprocessor::Processor::new(),
resource_manager: rm,
}
}
@ -518,7 +523,7 @@ impl RenderGraph {
/// This step also parses the shader and will return errors if it failed to parse.
///
/// Returns: The shader module import path if the module specified one.
#[inline(always)]
/* #[inline(always)]
pub fn register_shader(&mut self, shader_src: &str) -> Result<Option<String>, wgsl_preprocessor::Error> {
self.shader_prepoc.parse_module(shader_src)
}
@ -527,6 +532,16 @@ impl RenderGraph {
#[inline(always)]
pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> {
self.shader_prepoc.preprocess_module(shader_path)
} */
/// Load a shader from a `str`
///
/// This will also wait until the shader is loaded before returning.
pub fn load_shader_str(&self, shader_name: &str, shader_src: &str) -> Result<ResHandle<Shader>, RequestError> {
let rm = self.resource_manager.get::<ResourceManager>();
let shader = rm.load_str(shader_name, "text/wgsl", shader_src)?;
shader.wait_for_load()?;
Ok(shader)
}
}

View File

@ -2,18 +2,17 @@ use std::sync::Arc;
use glam::UVec2;
use lyra_game_derive::RenderGraphLabel;
use lyra_math::Transform;
use tracing::warn;
use winit::dpi::PhysicalSize;
use crate::{
render::{
camera::{CameraUniform, RenderCamera},
camera::CameraUniform,
graph::{
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
},
render_buffer::BufferWrapper, texture::RenderTexture,
},
scene::CameraComponent,
}, scene::{Camera, CameraProjection},
};
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
@ -114,15 +113,20 @@ impl Node for BasePass {
}
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
if let Some(camera) = world.view_iter::<&mut CameraComponent>().next() {
let mut found_camera = false;
for (camera, projection, transform) in world.view_iter::<(&Camera, &CameraProjection, &Transform)>() {
if camera.is_active {
let screen_size = graph.view_target().size();
let uniform = CameraUniform::from_component(*transform, *projection, screen_size.as_vec2());
context.queue_buffer_write_with(BasePassSlots::Camera, 0, uniform);
let mut render_cam =
RenderCamera::new(PhysicalSize::new(screen_size.x, screen_size.y));
let uniform = render_cam.calc_view_projection(&camera);
found_camera = true;
break;
}
}
context.queue_buffer_write_with(BasePassSlots::Camera, 0, uniform)
} else {
if !found_camera {
warn!("Missing camera!");
}
}

View File

@ -1,10 +1,10 @@
use std::{collections::HashMap, rc::Rc, sync::Arc};
use std::{collections::HashMap, sync::Arc};
use lyra_game_derive::RenderGraphLabel;
use crate::render::{
graph::{Node, NodeDesc, NodeType},
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, VertexState},
};
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
@ -64,10 +64,8 @@ impl Node for FxaaPass {
..Default::default()
}));
let shader = Rc::new(Shader {
label: Some("fxaa_shader".into()),
source: include_str!("../../shaders/fxaa.wgsl").to_string(),
});
let shader = graph.load_shader_str("fxaa_shader", include_str!("../../shaders/fxaa.wgsl"))
.expect("failed to load wgsl shader from manager");
let vt = graph.view_target();

View File

@ -1,4 +1,4 @@
use std::{mem, rc::Rc, sync::Arc};
use std::{mem, sync::Arc};
use glam::Vec2Swizzles;
use lyra_ecs::World;
@ -8,7 +8,7 @@ use wgpu::util::DeviceExt;
use crate::render::{
graph::{
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
}, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor, Shader}
}, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor}
};
use super::{BasePassSlots, LightBasePassSlots};
@ -219,10 +219,8 @@ impl Node for LightCullComputePass {
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
let light_indices_bg_layout = graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
let shader = Rc::new(Shader {
label: Some("light_cull_comp_shader".into()),
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
});
let shader = graph.load_shader_str("light_cull_comp_shader", include_str!("../../shaders/light_cull.comp.wgsl"))
.expect("failed to load light cull compute shader");
let pipeline = ComputePipeline::create(device, &ComputePipelineDescriptor {
label: Some("light_cull_pipeline".into()),

View File

@ -1,4 +1,4 @@
use std::{rc::Rc, sync::Arc};
use std::sync::Arc;
use lyra_ecs::{AtomicRef, ResourceData};
use lyra_game_derive::RenderGraphLabel;
@ -7,7 +7,7 @@ use tracing::{instrument, warn};
use crate::render::{
desc_buf_lay::DescVertexBufferLayout,
graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext},
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
texture::RenderTexture,
transform_buffer_storage::TransformBuffers,
vertex::Vertex,
@ -102,10 +102,8 @@ impl Node for MeshPass {
_: &mut RenderGraphContext,
) {
if self.pipeline.is_none() {
let shader_mod = graph.register_shader(include_str!("../../shaders/base.wgsl"))
.expect("failed to register shader").expect("base shader missing module");
let shader_src = graph.preprocess_shader(&shader_mod)
.expect("failed to preprocess shader");
let shader = graph.load_shader_str("mesh_base", include_str!("../../shaders/base.wgsl"))
.expect("failed to load base mesh shader");
let device = graph.device();
let surface_config_format = graph.view_target().format();
@ -299,11 +297,6 @@ impl Node for MeshPass {
graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
let atlas_bgl = self.shadows_atlas.as_ref().unwrap().layout.clone();
let shader = Rc::new(Shader {
label: Some(shader_mod.into()),
source: shader_src,
});
let transforms = world
.get_resource_data::<TransformBuffers>()
.expect("Missing transform buffers");
@ -414,12 +407,12 @@ impl Node for MeshPass {
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
load: wgpu::LoadOp::Load,/* wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
}), */
store: wgpu::StoreOp::Store,
},
})],
@ -441,9 +434,9 @@ impl Node for MeshPass {
for job in render_meshes.iter() {
// get the mesh (containing vertices) and the buffers from storage
let buffers = mesh_buffers.get(&job.mesh_uuid);
let buffers = mesh_buffers.get(&job.asset_uuid);
if buffers.is_none() {
warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid);
warn!("Skipping job since its mesh is missing {:?}", job.asset_uuid);
continue;
}
let buffers = buffers.unwrap();

View File

@ -30,3 +30,6 @@ pub use mesh_prepare::*;
mod transform;
pub use transform::*;
mod sprite;
pub use sprite::*;

View File

@ -14,17 +14,13 @@ use lyra_ecs::{
};
use lyra_game_derive::RenderGraphLabel;
use lyra_math::{Angle, Transform};
use lyra_resource::{RequestError, ResHandle};
use rustc_hash::FxHashMap;
use tracing::{debug, warn};
use wgpu::util::DeviceExt;
use crate::render::{
graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue},
light::{directional::DirectionalLight, LightType, PointLight, SpotLight},
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
transform_buffer_storage::TransformBuffers,
vertex::Vertex,
AtlasFrame, GpuSlotBuffer, TextureAtlas,
graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue}, light::{directional::DirectionalLight, LightType, PointLight, SpotLight}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState}, transform_buffer_storage::TransformBuffers, vertex::Vertex, AtlasFrame, GpuSlotBuffer, Shader, TextureAtlas
};
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
@ -74,7 +70,7 @@ pub struct ShadowMapsPass {
transform_buffers: Option<ResourceData>,
render_meshes: Option<ResourceData>,
mesh_buffers: Option<ResourceData>,
shader: Option<String>,
shader: ResHandle<Shader>,
pipeline: Option<RenderPipeline>,
point_light_pipeline: Option<RenderPipeline>,
@ -171,7 +167,7 @@ impl ShadowMapsPass {
transform_buffers: None,
render_meshes: None,
mesh_buffers: None,
shader: None,
shader: Default::default(),
pipeline: None,
point_light_pipeline: None,
@ -503,21 +499,14 @@ impl ShadowMapsPass {
queue.write_buffer(buffer, 0, bytemuck::cast_slice(points.as_slice()));
}
/// Register all the shaders, returning the module of the
fn register_shaders(&self, graph: &mut RenderGraph) -> Result<(), wgsl_preprocessor::Error> {
let src = include_str!("../../shaders/shadows/shadows_structs.wgsl");
graph.register_shader(src)?;
/// Load all shadow related registers and return the shader for the shadow depth pass.
fn loader_shaders(&self, graph: &mut RenderGraph) -> Result<ResHandle<Shader>, RequestError> {
graph.load_shader_str("shadows_structs", include_str!("../../shaders/shadows/shadows_structs.wgsl"))?;
graph.load_shader_str("shadows_bindings", include_str!("../../shaders/shadows/shadows_bindings.wgsl"))?;
graph.load_shader_str("shadows_calc", include_str!("../../shaders/shadows/shadows_calc.wgsl"))?;
let depth = graph.load_shader_str("shadows_depth", include_str!("../../shaders/shadows/shadows_depth.wgsl"))?;
let src = include_str!("../../shaders/shadows/shadows_bindings.wgsl");
graph.register_shader(src)?;
let src = include_str!("../../shaders/shadows/shadows_calc.wgsl");
graph.register_shader(src)?;
let src = include_str!("../../shaders/shadows/shadows_depth.wgsl");
graph.register_shader(src)?;
Ok(())
Ok(depth)
}
}
@ -526,11 +515,8 @@ impl Node for ShadowMapsPass {
&mut self,
graph: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::NodeDesc {
self.register_shaders(graph)
.expect("failed to register shaders");
self.shader = Some(graph.preprocess_shader("lyra::shadows::depth_pass")
.expect("failed to preprocess depth shadow shaders"));
println!("{}", self.shader.as_ref().unwrap());
self.shader = self.loader_shaders(graph)
.expect("failed to load depth shadow shader");
let mut node = NodeDesc::new(NodeType::Render, None, vec![]);
@ -773,11 +759,6 @@ impl Node for ShadowMapsPass {
}
if self.pipeline.is_none() {
let shader = Rc::new(Shader {
label: Some("lyra::shadows::depth_pass".into()),
source: self.shader.clone().unwrap(),
});
let bgl = self.bgl.clone();
let transforms = self.transform_buffers().bindgroup_layout.clone();
@ -788,7 +769,7 @@ impl Node for ShadowMapsPass {
layouts: vec![bgl.clone(), transforms.clone()],
push_constant_ranges: vec![],
vertex: VertexState {
module: shader.clone(),
module: self.shader.clone(),
entry_point: "vs_main".into(),
buffers: vec![Vertex::position_desc().into()],
},
@ -817,12 +798,12 @@ impl Node for ShadowMapsPass {
layouts: vec![bgl, transforms],
push_constant_ranges: vec![],
vertex: VertexState {
module: shader.clone(),
module: self.shader.clone(),
entry_point: "vs_main".into(),
buffers: vec![Vertex::position_desc().into()],
},
fragment: Some(FragmentState {
module: shader,
module: self.shader.clone(),
entry_point: "fs_point_light_main".into(),
targets: vec![],
}),
@ -963,9 +944,9 @@ fn light_shadow_pass_impl<'a>(
for job in render_meshes.iter() {
// get the mesh (containing vertices) and the buffers from storage
let buffers = mesh_buffers.get(&job.mesh_uuid);
let buffers = mesh_buffers.get(&job.asset_uuid);
if buffers.is_none() {
warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid);
warn!("Skipping job since its mesh is missing {:?}", job.asset_uuid);
continue;
}
let buffers = buffers.unwrap();

View File

@ -0,0 +1,403 @@
use std::{
collections::VecDeque,
sync::Arc,
};
use glam::{Vec2, Vec3};
use image::GenericImageView;
use lyra_ecs::{
query::{Entities, ResMut}, AtomicRef, ResourceData
};
use lyra_game_derive::RenderGraphLabel;
use lyra_resource::Image;
use tracing::{info, instrument, warn};
use uuid::Uuid;
use wgpu::util::DeviceExt;
use crate::{
render::{
graph::{Node, NodeDesc, NodeType, SlotAttribute},
render_job::RenderJob,
resource::{
FragmentState, RenderPipeline, RenderPipelineDescriptor,
VertexState,
},
transform_buffer_storage::{TransformBuffers, TransformIndex},
vertex::Vertex2D,
},
sprite::Sprite,
};
use super::{BasePassSlots, RenderAssets};
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
pub struct SpritePassLabel;
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
pub enum SpritePassSlots {
SpriteTexture,
SpriteTextureView,
SpriteTextureSampler,
}
struct SpriteTexture {
// this field is actually read, its given to the bind group
#[allow(dead_code)]
texture: wgpu::Texture,
// this field is actually read, its given to the bind group
#[allow(dead_code)]
sampler: wgpu::Sampler,
texture_bg: Arc<wgpu::BindGroup>,
vertex_buffers: wgpu::Buffer,
index_buffers: wgpu::Buffer,
}
#[derive(Default)]
pub struct SpritePass {
pipeline: Option<RenderPipeline>,
texture_bgl: Option<Arc<wgpu::BindGroupLayout>>,
jobs: VecDeque<RenderJob>,
transform_buffers: Option<ResourceData>,
sprite_textures: Option<ResourceData>,
}
impl SpritePass {
pub fn new() -> Self {
Self::default()
}
#[instrument(skip(self, device, sprite))]
fn create_vertex_index_buffers(
&mut self,
device: &wgpu::Device,
sprite: &Sprite,
) -> (wgpu::Buffer, wgpu::Buffer) {
let tex_dims = sprite
.texture
.data_ref()
.map(|t| t.dimensions());
if tex_dims.is_none() {
info!("Sprite texture is not loaded, not rendering it until it is!");
todo!("Wait until texture is loaded");
}
let tex_dims = tex_dims.unwrap();
let vertices = vec![
// top left
Vertex2D::new(Vec3::new(0.0, 0.0, 0.0), Vec2::new(0.0, 1.0)),
// bottom left
Vertex2D::new(Vec3::new(0.0, tex_dims.1 as f32, 0.0), Vec2::new(0.0, 0.0)),
// top right
Vertex2D::new(Vec3::new(tex_dims.0 as f32, 0.0, 0.0), Vec2::new(1.0, 1.0)),
// bottom right
Vertex2D::new(
Vec3::new(tex_dims.0 as f32, tex_dims.1 as f32, 0.0),
Vec2::new(1.0, 0.0),
),
];
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(vertices.as_slice()),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
let contents: [u32; 6] = [
//3, 1, 0,
//0, 2, 3
3, 1, 0, // second tri
0, 2, 3, // first tri
//0, 2, 3, // second tri
];
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(&contents),
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
});
(vertex_buffer, index_buffer)
}
fn load_sprite_texture(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
uuid: &Uuid,
image: &Image,
) -> Option<(wgpu::Texture, wgpu::Sampler, wgpu::BindGroup)> {
let uuid_str = uuid.to_string();
let image_dim = image.dimensions();
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some(&format!("sprite_texture_{}", uuid_str)),
size: wgpu::Extent3d {
width: image_dim.0,
height: image_dim.1,
depth_or_array_layers: 1,
},
mip_level_count: 1, // TODO
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let tex_view = tex.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &tex,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&image.to_rgba8(),
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * image_dim.0),
rows_per_image: Some(image_dim.1),
},
wgpu::Extent3d {
width: image_dim.0,
height: image_dim.1,
depth_or_array_layers: 1,
},
);
let bgl = self.texture_bgl.as_ref().unwrap();
let tex_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("sprite_texture_bg_{}", uuid_str)),
layout: bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&tex_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
});
Some((tex, sampler, tex_bg))
}
}
impl Node for SpritePass {
fn desc(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::NodeDesc {
let device = &graph.device;
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("bgl_sprite_main"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
self.texture_bgl = Some(Arc::new(bgl));
let mut desc = NodeDesc::new(NodeType::Render, None, vec![]);
desc.add_buffer_slot(BasePassSlots::Camera, SlotAttribute::Input, None);
desc
}
fn prepare(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
world: &mut lyra_ecs::World,
_: &mut crate::render::graph::RenderGraphContext,
) {
let device = graph.device();
let vt = graph.view_target();
if self.pipeline.is_none() {
let shader = graph.load_shader_str("sprite_shader", include_str!("../../shaders/2d/sprite_main.wgsl"))
.expect("failed to load wgsl shader from manager");
let diffuse_bgl = self.texture_bgl.clone().unwrap();
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera).clone();
let transforms = world
.get_resource::<TransformBuffers>()
.expect("Missing transform buffers");
let transform_bgl = transforms.bindgroup_layout.clone();
self.pipeline = Some(RenderPipeline::create(
device,
&RenderPipelineDescriptor {
label: Some("sprite_pass".into()),
layouts: vec![diffuse_bgl, transform_bgl, camera_bgl],
push_constant_ranges: vec![],
vertex: VertexState {
module: shader.clone(),
entry_point: "vs_main".into(),
buffers: vec![Vertex2D::desc().into()],
},
fragment: Some(FragmentState {
module: shader,
entry_point: "fs_main".into(),
targets: vec![Some(wgpu::ColorTargetState {
format: vt.format(),
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
depth_stencil: None,
primitive: wgpu::PrimitiveState {
cull_mode: Some(wgpu::Face::Back),
..Default::default()
},
multisample: wgpu::MultisampleState::default(),
multiview: None,
},
));
drop(transforms);
world.add_resource_default_if_absent::<RenderAssets<SpriteTexture>>();
let sprite_textures = world
.get_resource_data::<RenderAssets<SpriteTexture>>()
.expect("Missing sprite texture store");
self.sprite_textures = Some(sprite_textures.clone());
let transforms = world
.get_resource_data::<TransformBuffers>()
.expect("Missing transform buffers");
self.transform_buffers = Some(transforms.clone());
}
let queue = &graph.queue;
for (entity, sprite, transform_idx, mut sprite_store) in world
.view::<(
Entities,
&Sprite,
&TransformIndex,
ResMut<RenderAssets<SpriteTexture>>,
)>()
.iter()
{
if let Some(image) = sprite.texture.data_ref() {
let texture_uuid = sprite.texture.uuid();
if !sprite_store.contains_key(&texture_uuid) {
// returns `None` if the Texture image is not loaded.
if let Some((texture, sampler, tex_bg)) =
self.load_sprite_texture(device, queue, &texture_uuid, &image)
{
let (vertex, index) = self.create_vertex_index_buffers(device, &sprite);
sprite_store.insert(
texture_uuid,
SpriteTexture {
texture,
sampler,
texture_bg: Arc::new(tex_bg),
vertex_buffers: vertex,
index_buffers: index,
},
);
}
}
self.jobs.push_back(RenderJob {
entity,
shader_id: 0,
asset_uuid: texture_uuid,
transform_id: *transform_idx,
});
}
}
}
fn execute(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
_: &crate::render::graph::NodeDesc,
context: &mut crate::render::graph::RenderGraphContext,
) {
let pipeline = self.pipeline.as_ref().unwrap();
let sprite_store = self.sprite_textures.clone().unwrap();
let sprite_store: AtomicRef<RenderAssets<SpriteTexture>> = sprite_store.get();
let transforms = self.transform_buffers.clone().unwrap();
let transforms: AtomicRef<TransformBuffers> = transforms.get();
let vt = graph.view_target();
let view = vt.render_view();
let camera_bg = graph.bind_group(BasePassSlots::Camera);
{
let encoder = context.encoder.as_mut().unwrap();
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("sprite_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
pass.set_pipeline(pipeline);
while let Some(job) = self.jobs.pop_front() {
let sprite = sprite_store.get(&job.asset_uuid)
.expect("failed to find SpriteTexture for job asset_uuid");
pass.set_bind_group(0, &sprite.texture_bg, &[]);
// Get the bindgroup for job's transform and bind to it using an offset.
let bindgroup = transforms.bind_group(job.transform_id);
let offset = transforms.buffer_offset(job.transform_id);
pass.set_bind_group(1, bindgroup, &[offset]);
pass.set_bind_group(2, camera_bg, &[]);
pass.set_vertex_buffer(
0,
sprite.vertex_buffers.slice(..),
);
pass.set_index_buffer(sprite.index_buffers.slice(..), wgpu::IndexFormat::Uint32);
pass.draw_indexed(0..6, 0, 0..1);
}
}
}
}

View File

@ -1,10 +1,10 @@
use std::{collections::HashMap, rc::Rc, sync::Arc};
use std::{collections::HashMap, sync::Arc};
use lyra_game_derive::RenderGraphLabel;
use crate::render::{
graph::{Node, NodeDesc, NodeType},
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, VertexState},
};
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
@ -58,13 +58,9 @@ impl Node for TintPass {
self.bgl = Some(bgl.clone());
self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default()));
let shader = Rc::new(Shader {
label: Some("tint_shader".into()),
source: include_str!("../../shaders/tint.wgsl").to_string(),
});
let vt = graph.view_target();
let shader = graph.load_shader_str("tint_shader", include_str!("../../shaders/tint.wgsl"))
.expect("failed to load tint shader");
NodeDesc::new(
NodeType::Render,

View File

@ -32,12 +32,12 @@ pub struct InterpTransform {
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
pub struct TransformsNodeLabel;
#[derive(Debug)]
pub struct TransformsNode {}
#[derive(Debug, Default)]
pub struct TransformsNode;
impl TransformsNode {
pub fn new() -> Self {
Self {}
Self
}
}

View File

@ -6,7 +6,6 @@ pub mod render_buffer;
pub mod render_job;
pub mod mesh;
pub mod texture;
pub mod shader_loader;
pub mod material;
pub mod camera;
pub mod transform_buffer_storage;
@ -20,3 +19,6 @@ pub use texture_atlas::*;
mod slot_buffer;
pub use slot_buffer::*;
mod shader_loader;
pub use shader_loader::*;

View File

@ -5,7 +5,7 @@ use super::transform_buffer_storage::TransformIndex;
pub struct RenderJob {
pub entity: Entity,
pub shader_id: u64,
pub mesh_uuid: uuid::Uuid,
pub asset_uuid: uuid::Uuid,
pub transform_id: TransformIndex,
}
@ -14,7 +14,7 @@ impl RenderJob {
Self {
entity,
shader_id,
mesh_uuid: mesh_buffer_id,
asset_uuid: mesh_buffer_id,
transform_id
}
}

View File

@ -9,7 +9,7 @@ use lyra_game_derive::RenderGraphLabel;
use tracing::{debug, instrument, warn};
use winit::window::Window;
use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, ShadowMapsPass, ShadowMapsPassLabel, SubGraphNode, TransformsNode, TransformsNodeLabel, ViewTarget};
use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, ShadowMapsPass, ShadowMapsPassLabel, SpritePass, SpritePassLabel, SubGraphNode, TransformsNode, TransformsNodeLabel, ViewTarget};
use super::graph::RenderGraph;
use super::{resource::RenderPipeline, render_job::RenderJob};
@ -135,13 +135,13 @@ impl BasicRenderer {
let surface_target = RenderTarget::from_surface(surface, config);
let view_target = Rc::new(RefCell::new(ViewTarget::new(device.clone(), surface_target)));
let mut main_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone());
let mut main_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone(), &world);
debug!("Adding base pass");
main_graph.add_node(BasePassLabel, BasePass::new());
{
let mut forward_plus_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone());
let mut forward_plus_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone(), &world);
debug!("Adding light base pass");
forward_plus_graph.add_node(LightBasePassLabel, LightBasePass::new());
@ -163,6 +163,10 @@ impl BasicRenderer {
forward_plus_graph.add_node(MeshesPassLabel, MeshPass::new(material_bgl));
forward_plus_graph.add_edge(TransformsNodeLabel, MeshPrepNodeLabel);
debug!("Adding sprite pass");
forward_plus_graph.add_node(SpritePassLabel, SpritePass::new());
forward_plus_graph.add_edge(TransformsNodeLabel, SpritePassLabel);
forward_plus_graph.add_edge(LightBasePassLabel, LightCullComputePassLabel);
forward_plus_graph.add_edge(LightCullComputePassLabel, MeshesPassLabel);
forward_plus_graph.add_edge(MeshPrepNodeLabel, MeshesPassLabel);

View File

@ -1,16 +1,18 @@
use std::{ops::Deref, rc::Rc, sync::Arc};
use lyra_resource::ResHandle;
use wgpu::PipelineLayout;
use super::{PipelineCompilationOptions, Shader};
use crate::render::Shader;
use super::PipelineCompilationOptions;
//#[derive(Debug, Clone)]
pub struct ComputePipelineDescriptor {
pub label: Option<String>,
pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub shader: Rc<Shader>,
pub shader: ResHandle<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub shader_entry_point: String,
@ -76,13 +78,16 @@ impl ComputePipeline {
None
};
let shader = desc.shader.data_ref()
.expect("shader is not loaded, ensure its loaded before creating a pipeline!");
// an Rc was used here so that this shader could be reused by the fragment stage if
// they share the same shader. I tried to do it without an Rc but couldn't get past
// the borrow checker
let compiled_shader = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: desc.shader.label.as_deref(),
label: shader.label.as_deref(),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
&desc.shader.source,
&shader.source,
)),
}));

View File

@ -84,13 +84,16 @@ impl RenderPipeline {
})
.collect::<Vec<_>>();
let vertex_shader = desc.vertex.module.data_ref()
.expect("vertex shader is not loaded, ensure its loaded before creating a pipeline!");
// an Rc was used here so that this shader could be reused by the fragment stage if
// they share the same shader. I tried to do it without an Rc but couldn't get past
// the borrow checker
let vrtx_shad = Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: desc.vertex.module.label.as_deref(),
label: vertex_shader.label.as_deref(),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
&desc.vertex.module.source,
&vertex_shader.source,
)),
}));
let vrtx_state = wgpu::VertexState {
@ -101,12 +104,14 @@ impl RenderPipeline {
};
let frag_module = desc.fragment.as_ref().map(|f| {
if f.module == desc.vertex.module {
if f.module.uuid() == desc.vertex.module.uuid() {
vrtx_shad.clone()
} else {
let frag_shader = f.module.data_ref()
.expect("vertex shader is not loaded, ensure its loaded before creating a pipeline!");
Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: f.module.label.as_deref(),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)),
label: frag_shader.label.as_deref(),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&frag_shader.source)),
}))
}
});

View File

@ -1,4 +1,6 @@
use std::rc::Rc;
use lyra_resource::ResHandle;
use crate::render::Shader;
#[derive(Debug, Default, Clone)]
pub struct VertexBufferLayout {
@ -18,11 +20,10 @@ impl<'a> From<wgpu::VertexBufferLayout<'a>> for VertexBufferLayout {
}
/// Describes the vertex stage in a render pipeline.
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct VertexState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub module: Rc<Shader>,
pub module: ResHandle<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub entry_point: String,
@ -30,18 +31,11 @@ pub struct VertexState {
pub buffers: Vec<VertexBufferLayout>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shader {
pub label: Option<String>,
pub source: String,
}
/// Describes the fragment stage in the render pipeline.
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct FragmentState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub module: Rc<Shader>,
pub module: ResHandle<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub entry_point: String,

View File

@ -0,0 +1,97 @@
use std::sync::Arc;
use async_std::sync::RwLock;
use lyra_resource::{loader::{LoaderError, ResourceLoader}, ResHandle, ResourceData};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shader {
pub label: Option<String>,
pub source: String,
pub dependent_modules: Option<String>,
}
impl ResourceData for Shader {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn dependencies(&self) -> Vec<lyra_resource::UntypedResHandle> {
vec![]
}
}
/// A WGSL shader module loader with a preprocessor.
///
/// This will load resources with mimetype of `text/wgsl` and `.wgsl` file extensions.
#[derive(Default)]
pub struct PreprocessShaderLoader {
processor: Arc<RwLock<wgsl_preprocessor::Processor>>,
}
async fn load_shader_str(processor: Arc<RwLock<wgsl_preprocessor::Processor>>, content: &str) -> Result<Shader, LoaderError> {
let mut processor = processor.write().await;
let mod_path = processor.parse_module(content)
.map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?;
let content = match &mod_path {
Some(path) => processor.preprocess_module(&path)
.map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?,
None => content.to_owned(),
};
Ok(Shader {
label: mod_path,
source: content,
dependent_modules: None,
})
}
impl ResourceLoader for PreprocessShaderLoader {
fn extensions(&self) -> &[&str] {
&[
"wgsl"
]
}
fn mime_types(&self) -> &[&str] {
&[
"text/wgsl"
]
}
fn load(&self, _: lyra_resource::ResourceManager, path: &str) -> lyra_resource::loader::PinedBoxLoaderFuture {
// cant use &str across async
let path = path.to_string();
let processor = self.processor.clone();
Box::pin(async move {
let module_str = std::fs::read_to_string(&path)
.map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?;
let shader = load_shader_str(processor, &module_str).await?;
Ok(Box::new(shader) as Box<dyn ResourceData>)
})
}
fn load_bytes(&self, _: lyra_resource::ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> lyra_resource::loader::PinedBoxLoaderFuture {
let processor = self.processor.clone();
Box::pin(async move {
let end = offset + length;
let contents = std::str::from_utf8(&bytes[offset..end])
.map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?;
let shader = load_shader_str(processor, &contents).await?;
Ok(Box::new(shader) as Box<dyn ResourceData>)
})
}
fn create_erased_handle(&self) -> std::sync::Arc<dyn lyra_resource::ResourceStorage> {
Arc::from(ResHandle::<Shader>::new_loading(None))
}
}

View File

@ -0,0 +1,60 @@
const ALPHA_CUTOFF = 0.1;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
@location(1) world_position: vec3<f32>,
}
struct TransformData {
transform: mat4x4<f32>,
normal_matrix: mat4x4<f32>,
}
struct CameraUniform {
view: mat4x4<f32>,
inverse_projection: mat4x4<f32>,
view_projection: mat4x4<f32>,
projection: mat4x4<f32>,
position: vec3<f32>,
tile_debug: u32,
}
@group(1) @binding(0)
var<uniform> u_model_transform_data: TransformData;
@group(2) @binding(0)
var<uniform> u_camera: CameraUniform;
@vertex
fn vs_main(
in: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
var world_position: vec4<f32> = u_model_transform_data.transform * vec4<f32>(in.position, 1.0);
out.world_position = world_position.xyz;
out.tex_coords = in.tex_coords;
out.clip_position = u_camera.view_projection * world_position;
return out;
}
@group(0) @binding(0)
var t_diffuse: texture_2d<f32>;
@group(0) @binding(1)
var s_diffuse: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
if (object_color.a < ALPHA_CUTOFF) {
discard;
}
return object_color;
}

View File

@ -16,7 +16,7 @@ struct CameraUniform {
projection: mat4x4<f32>,
position: vec3f,
tile_debug: u32,
};
}
struct Light {
position: vec3f,
@ -32,12 +32,12 @@ struct Light {
spot_cutoff: f32,
spot_outer_cutoff: f32,
light_shadow_uniform_index: array<i32, 6>,
};
}
struct Lights {
light_count: u32,
data: array<Light>,
};
}
struct Cone {
tip: vec3f,

View File

@ -75,7 +75,7 @@ impl Vertex2D {
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
array_stride: std::mem::size_of::<Vertex2D>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {

View File

@ -1,36 +1,127 @@
use lyra_ecs::Component;
use glam::{Mat4, Vec2};
use lyra_ecs::{Bundle, Component};
use lyra_math::Angle;
use lyra_reflect::Reflect;
use crate::{math::{Angle, Transform}, render::camera::CameraProjectionMode};
#[derive(Clone, Component, Reflect)]
pub struct CameraComponent {
pub transform: Transform,
pub fov: Angle,
pub mode: CameraProjectionMode,
pub debug: bool,
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
pub struct OrthographicProjection {
pub scale: f32,
pub znear: f32,
pub zfar: f32,
}
impl Default for CameraComponent {
impl Default for OrthographicProjection {
fn default() -> Self {
Self {
scale: 1.0,
znear: 0.0,
zfar: 1000.0,
}
}
}
impl OrthographicProjection {
pub fn to_mat(&self, viewport_size: Vec2) -> Mat4 {
let origin_x = viewport_size.x as f32 * 0.5;
let origin_y = viewport_size.y as f32 * 0.5;
glam::Mat4::orthographic_rh(
self.scale * -origin_x,
self.scale * viewport_size.x as f32 - origin_x,
self.scale * -origin_y,
self.scale * viewport_size.y as f32 - origin_y,
-1000.0,
1000.0,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
pub struct PerspectiveProjection {
pub fov: Angle,
pub znear: f32,
pub zfar: f32,
}
impl Default for PerspectiveProjection {
fn default() -> Self {
Self {
transform: Transform::default(),
fov: Angle::Degrees(45.0),
mode: CameraProjectionMode::Perspective,
debug: false,
znear: 0.1,
zfar: 100.0,
}
}
}
impl CameraComponent {
pub fn new_3d() -> Self {
Self::default()
impl PerspectiveProjection {
pub fn to_mat(&self, viewport_size: Vec2) -> Mat4 {
let aspect = viewport_size.x / viewport_size.y;
glam::Mat4::perspective_rh(self.fov.to_radians(), aspect, self.znear, self.zfar)
}
}
pub fn new_2d() -> Self {
CameraComponent {
mode: CameraProjectionMode::Orthographic,
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Component)]
pub enum CameraProjection {
/// 3d camera projection
Perspective(PerspectiveProjection),
/// 2d camera projection
Orthographic(OrthographicProjection),
}
impl Default for CameraProjection {
fn default() -> Self {
Self::Perspective(PerspectiveProjection::default())
}
}
impl CameraProjection {
pub fn to_mat4(&self, viewport_size: Vec2) -> Mat4 {
match self {
CameraProjection::Perspective(proj) => proj.to_mat(viewport_size),
CameraProjection::Orthographic(proj) => proj.to_mat(viewport_size),
}
}
}
#[derive(Clone, Default, Component, Reflect)]
pub struct Camera2d;
#[derive(Clone, Component, Reflect)]
pub struct Camera {
pub is_active: bool,
}
impl Default for Camera {
fn default() -> Self {
Self { is_active: true }
}
}
/// A component bundle for a Camera entity.
#[derive(Clone, Default, Bundle)]
pub struct CameraBundle {
pub camera: Camera,
pub projection: CameraProjection,
}
/// A component bundle for a 2d Camera entity.
#[derive(Clone, Bundle)]
pub struct Camera2dBundle {
pub camera: Camera,
pub projection: CameraProjection,
pub camera_2d: Camera2d,
}
impl Default for Camera2dBundle {
fn default() -> Self {
Self {
camera: Default::default(),
projection: CameraProjection::Orthographic(OrthographicProjection {
znear: -1000.0,
zfar: 1000.0,
..Default::default()
}),
camera_2d: Default::default(),
}
}
}

View File

@ -1,11 +1,10 @@
use glam::{EulerRot, Quat, Vec3};
use lyra_ecs::{query::{Res, View}, Component};
use lyra_math::Transform;
use lyra_reflect::Reflect;
use crate::{game::App, input::ActionHandler, plugin::Plugin, DeltaTime};
use super::CameraComponent;
pub const ACTLBL_MOVE_UP_DOWN: &str = "MoveUpDown";
pub const ACTLBL_MOVE_LEFT_RIGHT: &str = "MoveLeftRight";
pub const ACTLBL_MOVE_FORWARD_BACKWARD: &str = "MoveForwardBackward";
@ -40,11 +39,11 @@ impl FreeFlyCamera {
}
}
pub fn free_fly_camera_controller(delta_time: Res<DeltaTime>, handler: Res<ActionHandler>, view: View<(&mut CameraComponent, &FreeFlyCamera)>) -> anyhow::Result<()> {
pub fn free_fly_camera_controller(delta_time: Res<DeltaTime>, handler: Res<ActionHandler>, view: View<(&mut Transform, &FreeFlyCamera)>) -> anyhow::Result<()> {
let delta_time = **delta_time;
for (mut cam, fly) in view.into_iter() {
let forward = cam.transform.forward();
let left = cam.transform.left();
for (mut transform, fly) in view.into_iter() {
let forward = transform.forward();
let left = transform.left();
let up = Vec3::Y;
let move_y = handler.get_axis_modifier(ACTLBL_MOVE_UP_DOWN).unwrap_or(0.0);
@ -57,7 +56,7 @@ pub fn free_fly_camera_controller(delta_time: Res<DeltaTime>, handler: Res<Actio
velocity += move_z * forward;
if velocity != Vec3::ZERO {
cam.transform.translation += velocity.normalize() * fly.speed * delta_time; // TODO: speeding up
transform.translation += velocity.normalize() * fly.speed * delta_time; // TODO: speeding up
}
let motion_x = handler.get_axis_modifier(ACTLBL_LOOK_LEFT_RIGHT).unwrap_or(0.0);
@ -72,13 +71,13 @@ pub fn free_fly_camera_controller(delta_time: Res<DeltaTime>, handler: Res<Actio
if camera_rot != Vec3::ZERO {
let look_velocity = camera_rot * fly.look_speed * delta_time;
let (mut y, mut x, _) = cam.transform.rotation.to_euler(EulerRot::YXZ);
let (mut y, mut x, _) = transform.rotation.to_euler(EulerRot::YXZ);
x += look_velocity.x;
y += look_velocity.y;
x = x.clamp(-1.54, 1.54);
// rotation is not commutative, keep this order to avoid unintended roll
cam.transform.rotation = Quat::from_axis_angle(Vec3::Y, y)
transform.rotation = Quat::from_axis_angle(Vec3::Y, y)
* Quat::from_axis_angle(Vec3::X, x);
}
}

View File

@ -45,7 +45,7 @@ impl Pivot {
#[derive(Clone, Component, Reflect)]
pub struct Sprite {
pub texture: ResHandle<lyra_resource::Texture>,
pub texture: ResHandle<lyra_resource::Image>,
pub color: Vec3,
pub pivot: Pivot,
}

View File

@ -24,7 +24,7 @@ enum ModelLoaderError {
impl From<ModelLoaderError> for LoaderError {
fn from(value: ModelLoaderError) -> Self {
LoaderError::DecodingError(value.into())
LoaderError::DecodingError(Arc::new(value.into()))
}
}
@ -181,7 +181,7 @@ impl ResourceLoader for GltfLoader {
let parent_path = parent_path.display().to_string();
let gltf = gltf::Gltf::open(&path)
.map_err(|ge| LoaderError::DecodingError(ge.into()))?;
.map_err(|ge| LoaderError::DecodingError(Arc::new(ge.into())))?;
let mut use_bin = false;
let buffers: Vec<Vec<u8>> = gltf.buffers().flat_map(|b| match b.source() {
@ -290,7 +290,7 @@ mod tests {
let manager = ResourceManager::new();
let gltf = manager.request::<Gltf>(&path).unwrap();
assert!(gltf.wait_for_load_timeout(Duration::from_secs(10)), "failed to load gltf, hit 10 second timeout");
assert!(gltf.wait_for_load_timeout(Duration::from_secs(10)).is_ok_and(|r| r), "failed to load gltf, hit 10 second timeout");
let gltf = gltf.data_ref().unwrap();
assert_eq!(gltf.scenes.len(), 1);

View File

@ -10,7 +10,7 @@ pub fn radians_to_degrees(radians: f32) -> f32 {
radians * 180.0 / PI
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Angle {
Degrees(f32),
Radians(f32),

View File

@ -1,5 +1,3 @@
use std::sync::Arc;
use crate::{loader::LoaderError, ResourceState, UntypedResHandle};
#[derive(Clone)]
@ -9,7 +7,7 @@ pub enum DependencyState {
/// The resource that had the error.
handle: UntypedResHandle,
/// The error that the resource ran into when loading.
error: Arc<LoaderError>,
error: LoaderError,
},
Ready,
}

View File

@ -15,8 +15,6 @@ pub mod loader;
mod world_ext;
pub use world_ext::*;
pub(crate) mod util;
pub use crossbeam::channel as channel;
pub use notify;

View File

@ -10,7 +10,7 @@ use super::{LoaderError, PinedBoxLoaderFuture, ResourceLoader};
impl From<ImageError> for LoaderError {
fn from(value: ImageError) -> Self {
LoaderError::DecodingError(value.into())
LoaderError::DecodingError(Arc::new(value.into()))
}
}
@ -75,8 +75,8 @@ impl ResourceLoader for ImageLoader {
// load the image and construct Resource<Texture>
let image = image::load_from_memory(&buf)
.map_err(|e| match e {
ImageError::IoError(e) => LoaderError::IoError(e),
_ => LoaderError::DecodingError(e.into()),
ImageError::IoError(e) => LoaderError::IoError(Arc::new(e)),
_ => LoaderError::DecodingError(Arc::new(e.into())),
})?;
let image = Image::from(image);
let image = Box::new(image) as Box<dyn ResourceData>;
@ -89,8 +89,8 @@ impl ResourceLoader for ImageLoader {
Box::pin(async move {
let image = image::load_from_memory(&bytes[offset..(length-offset)])
.map_err(|e| match e {
ImageError::IoError(e) => LoaderError::IoError(e),
_ => LoaderError::DecodingError(e.into()),
ImageError::IoError(e) => LoaderError::IoError(Arc::new(e)),
_ => LoaderError::DecodingError(Arc::new(e.into())),
})?;
let image = Image::from(image);
debug!("Finished loading image ({} bytes)", length);

View File

@ -7,7 +7,7 @@ use thiserror::Error;
use crate::{resource_manager::ResourceStorage, ResourceData, ResourceManager};
#[derive(Error, Debug)]
#[derive(Error, Debug, Clone)]
pub enum LoaderError {
#[error("A malformed path was given: '{0}'")]
InvalidPath(String),
@ -16,16 +16,22 @@ pub enum LoaderError {
UnsupportedExtension(String),
#[error("IOError: '{0}'")]
IoError(io::Error),
IoError(Arc<io::Error>),
// From is implemented for this field in each loader module
#[error("Decoding error: '{0}'")]
DecodingError(anyhow::Error),
DecodingError(Arc<anyhow::Error>),
}
impl From<io::Error> for LoaderError {
fn from(value: io::Error) -> Self {
LoaderError::IoError(value)
LoaderError::IoError(Arc::new(value))
}
}
impl From<anyhow::Error> for LoaderError {
fn from(value: anyhow::Error) -> Self {
LoaderError::DecodingError(Arc::new(value))
}
}

View File

@ -48,7 +48,7 @@ pub trait ResourceData: Send + Sync + Any + 'static {
pub enum ResourceState {
Loading,
Error(Arc<LoaderError>),
Error(LoaderError),
Ready(Box<dyn ResourceData>),
}
@ -165,7 +165,7 @@ impl UntypedResHandle {
matches!(d.state, ResourceState::Ready(_))
}
pub fn get_error(&self) -> Option<Arc<LoaderError>> {
pub fn get_error(&self) -> Option<LoaderError> {
let d = self.read();
match &d.state {
@ -197,14 +197,15 @@ impl UntypedResHandle {
///
/// This blocks the thread without consuming CPU time; its backed by a
/// [`Condvar`](std::sync::Condvar).
pub fn wait_for_load(&self) {
self.wait_for_load_timeout_option_impl(None);
pub fn wait_for_load(&self) -> Result<(), LoaderError> {
self.wait_for_load_timeout_option_impl(None)?;
Ok(())
}
/// 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 {
pub fn wait_for_load_timeout(&self, timeout: Duration) -> Result<bool, LoaderError> {
self.wait_for_load_timeout_option_impl(Some(timeout))
}
@ -212,41 +213,48 @@ 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_recurse_dependencies_load_timeout_option_impl(None);
pub fn wait_recurse_dependencies_load(&self) -> Result<(), LoaderError> {
self.wait_recurse_dependencies_load_timeout_option_impl(None)?;
Ok(())
}
/// 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 {
pub fn wait_recurse_dependencies_load_timeout(&self, timeout: Duration) -> Result<bool, LoaderError> {
self.wait_recurse_dependencies_load_timeout_option_impl(Some(timeout))
}
fn wait_for_load_timeout_option_impl(&self, timeout: Option<Duration>) -> bool {
fn wait_for_load_timeout_option_impl(&self, timeout: Option<Duration>) -> Result<bool, LoaderError> {
let d = self.read();
if matches!(d.state, ResourceState::Ready(_)) {
return true;
return Ok(true);
}
let cv = d.condvar.clone();
// MUST DROP to avoid deadlock
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()
let (_unused, _) = cv.1.wait_timeout(l, timeout).unwrap();
} else {
let _unused = cv.1.wait(l).unwrap();
true
}
let d = self.read();
match &d.state {
// it will still be loading if the timeout is exceeded
ResourceState::Loading => Ok(false),
ResourceState::Error(e) => Err(e.clone()),
ResourceState::Ready(_) => Ok(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;
fn wait_recurse_dependencies_load_timeout_option_impl(&self, timeout: Option<Duration>) -> Result<bool, LoaderError> {
if !self.wait_for_load_timeout_option_impl(timeout)? {
return Ok(false);
}
let res = self.read();
@ -257,13 +265,13 @@ impl UntypedResHandle {
// waiting for some resources and finish early.
while self.recurse_dependency_state().is_loading() {
for dep in data.recur_dependencies() {
if !dep.wait_for_load_timeout_option_impl(timeout) {
return false;
if !dep.wait_for_load_timeout_option_impl(timeout)? {
return Ok(false);
}
}
}
true
Ok(true)
},
// self.wait_for_load at the start ensures that the state is ready
_ => unreachable!()
@ -315,6 +323,12 @@ pub struct ResHandle<T: ResourceData> {
_marker: PhantomData<T>,
}
impl<T: ResourceData> Default for ResHandle<T> {
fn default() -> Self {
Self::new_loading(None)
}
}
impl<T: ResourceData> Clone for ResHandle<T> {
fn clone(&self) -> Self {
Self {
@ -591,7 +605,7 @@ mod tests {
assert!(state.is_loading());
// this will take a bit
res.wait_recurse_dependencies_load();
res.wait_recurse_dependencies_load().unwrap();
let state = res.recurse_dependency_state();
assert!(!state.is_loading());

View File

@ -166,7 +166,8 @@ impl ResourceManager {
}
Err(err) => {
let mut d = untyped.write();
d.state = ResourceState::Error(Arc::new(err));
d.state = ResourceState::Error(err);
d.condvar.1.notify_all();
}
}
});
@ -236,10 +237,12 @@ impl ResourceManager {
Ok(data) => {
let mut d = thand.write();
d.state = ResourceState::Ready(data);
d.condvar.1.notify_all();
}
Err(err) => {
let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err));
d.state = ResourceState::Error(err);
d.condvar.1.notify_all();
}
}
});
@ -273,6 +276,15 @@ impl ResourceManager {
}
}
pub fn load_str<T>(&self, ident: &str, mime_type: &str, text: &str) -> Result<ResHandle<T>, RequestError>
where
T: ResourceData
{
let bytes = text.as_bytes().to_vec();
let len = bytes.len();
self.load_bytes(ident, mime_type, bytes, 0, len)
}
/// Start watching a path for changes. Returns a mspc channel that will send events
pub fn watch(&self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let (send, recv) = crossbeam::channel::bounded(15);
@ -358,7 +370,7 @@ impl ResourceManager {
}
Err(err) => {
let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err));
d.state = ResourceState::Error(err);
}
}
});
@ -370,7 +382,7 @@ impl ResourceManager {
#[cfg(test)]
pub mod tests {
use std::{io, ops::Deref};
use std::io;
use instant::Instant;
@ -420,8 +432,7 @@ pub mod tests {
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
assert!(!res.is_loaded());
res.wait_for_load();
//busy_wait_resource(&res, 10.0);
res.wait_for_load().unwrap();
// shouldn't panic because of the loop
res.data_ref().unwrap();
@ -455,7 +466,7 @@ pub mod tests {
// make sure the error is NotFound
//RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true,
ResourceState::Error(err) => {
match err.deref() {
match err {
LoaderError::IoError(e) if e.kind() == io::ErrorKind::NotFound => true,
_ => false,
}

View File

@ -1 +0,0 @@
use base64::Engine;

View File

@ -17,7 +17,8 @@ impl ScriptApiProvider for LyraEcsApiProvider {
world.register_lua_wrapper::<LuaActionHandler>();
world.register_lua_wrapper::<LuaWindow>();
world.register_lua_convert_component::<LuaCamera>("Camera");
// TODO: expose camera
// world.register_lua_convert_component::<LuaCamera>("Camera");
world.register_lua_convert_component::<LuaFreeFlyCamera>("FreeFlyCamera");
world.register_lua_convert_component::<LuaWorldTransform>("WorldTransform");
@ -54,7 +55,6 @@ impl ScriptApiProvider for LyraEcsApiProvider {
globals.set("TickOfQuery", ctx.create_proxy::<LuaTickOfQuery>()?)?;
globals.set("OptionalQuery", ctx.create_proxy::<LuaOptionalQuery>()?)?;
expose_comp_table_wrapper::<LuaCamera>(&ctx, &globals, "Camera")?;
expose_comp_table_wrapper::<LuaFreeFlyCamera>(&ctx, &globals, "FreeFlyCamera")?;
expose_comp_table_wrapper::<LuaWorldTransform>(&ctx, &globals, "WorldTransform")?;
expose_table_wrapper::<LuaDeviceEvent>(&ctx, &globals, "DeviceEvent")?;

View File

@ -1,19 +1,66 @@
use crate::{
lua::{
wrappers::{LuaAngle, LuaTransform},
LuaWrapper
wrappers::LuaAngle,
LuaWrapper,
},
ScriptBorrow,
};
use lyra_game::{render::camera::CameraProjectionMode, scene::CameraComponent};
use lyra_reflect::Enum;
use lyra_game::{
render::camera::{CameraProjectionMode, OrthographicProjection, PerspectiveProjection},
scene::CameraComponent,
};
use lyra_scripting_derive::to_lua_convert;
fn projection_mode_from_str(s: &str) -> Option<CameraProjectionMode> {
match s {
"perspective" => Some(CameraProjectionMode::Perspective),
"orthographic" => Some(CameraProjectionMode::Orthographic),
_ => None,
fn projection_as_table(lua: &mlua::Lua, projection: CameraProjectionMode) -> mlua::Result<mlua::Table> {
let table = lua.create_table()?;
match projection {
CameraProjectionMode::Perspective(proj) => {
table.set("type", "perspective")?;
table.set("fov", LuaAngle(proj.fov))?;
table.set("znear", proj.znear)?;
table.set("zfar", proj.zfar)?;
},
CameraProjectionMode::Orthographic(proj) => {
table.set("type", "orthographic")?;
table.set("scale", proj.scale)?;
table.set("znear", proj.znear)?;
table.set("zfar", proj.zfar)?;
},
}
Ok(table)
}
fn projection_from_table(t: &mlua::Table) -> mlua::Result<CameraProjectionMode> {
let ty: String = t.get("type")?;
let ty = ty.as_str();
let znear: f32 = t.get("znear")?;
let zfar: f32 = t.get("zfar")?;
match ty {
"perspective" => {
let fov: LuaAngle = t.get("fov")?;
Ok(CameraProjectionMode::Perspective(PerspectiveProjection {
fov: fov.0,
znear,
zfar,
}))
}
"orthographic" => {
let scale: f32 = t.get("scale")?;
Ok(CameraProjectionMode::Orthographic(OrthographicProjection {
scale,
znear,
zfar,
}))
}
_ => Err(mlua::Error::FromLuaConversionError {
from: "Table",
to: "CameraProjection".into(),
message: Some(format!("unknown projection type: '{}'", ty)),
}),
}
}
@ -27,20 +74,16 @@ to_lua_convert!(
// Reflection type, can be 'component' or 'resource'
reflect=component,
fields={
transform: wrap(LuaTransform),
fov: wrap(LuaAngle),
(
mode,
// Get the table from the value, result must be `CameraProjectionMode`
projection,
get={
let mode: String = table.get("mode")?;
projection_mode_from_str(&mode).unwrap()
let p: mlua::Table = table.get("projection")?;
projection_from_table(&p)?
},
// Get the value from self, result must be the type in Lua, here its `String`.
set={
self.mode.variant_name().to_lowercase()
}
),
debug: bool
projection_as_table(lua, self.projection)?
},
)
}
);

View File

@ -13,9 +13,6 @@ pub use delta_time::*;
mod window;
pub use window::*;
mod camera;
pub use camera::*;
mod free_fly_camera;
pub use free_fly_camera::*;

View File

@ -1,12 +1,17 @@
use lyra_engine::{
assets::ResourceManager, gltf::Gltf, ecs::query::View, game::App, input::{
assets::{Image, ResourceManager},
game::App,
gltf::Gltf,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId,
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
}, winit::WindowOptions
},
math::{self, Transform, Vec3},
render::light::directional::DirectionalLight,
scene::{
system_update_world_transforms, Camera2dBundle, CameraProjection, FreeFlyCamera, FreeFlyCameraPlugin, OrthographicProjection, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN
},
sprite::{self, Sprite},
};
#[async_std::main]
@ -85,7 +90,12 @@ async fn main() {
.with_plugin(setup_scene_plugin)
.with_plugin(action_handler_plugin)
//.with_plugin(camera_debug_plugin)
.with_plugin(FreeFlyCameraPlugin);
.with_plugin(FreeFlyCameraPlugin)
.with_system(
"system_update_world_transforms",
system_update_world_transforms,
&[],
);
a.run();
}
@ -111,16 +121,29 @@ fn setup_scene_plugin(app: &mut App) {
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
.unwrap();
cube_gltf.wait_recurse_dependencies_load();
cube_gltf.wait_recurse_dependencies_load().unwrap();
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
drop(resman);
let image = resman.request::<Image>("../assets/Egg_item.png").unwrap();
image.wait_recurse_dependencies_load().unwrap();
drop(resman);
world.spawn((
cube_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(0.0, 0.0, -2.0),
));
world.spawn((
Sprite {
texture: image,
color: Vec3::ONE,
pivot: sprite::Pivot::Center,
},
WorldTransform::default(),
Transform::from_xyz(0.0, 0.0, -10.0),
));
{
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
@ -136,7 +159,15 @@ fn setup_scene_plugin(app: &mut App) {
));
}
let mut camera = CameraComponent::new_2d();
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
world.spawn((camera, FreeFlyCamera::default()));
world.spawn((
Camera2dBundle {
projection: CameraProjection::Orthographic(OrthographicProjection {
scale: 0.75,
..Default::default()
}),
..Default::default()
},
Transform::from_xyz(200.0, 120.0, 0.0),
FreeFlyCamera::default(),
));
}

1
examples/assets/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Egg_item.png

View File

@ -1,25 +1,16 @@
use std::ptr::NonNull;
use lyra_engine::{
assets::ResourceManager, gltf::Gltf,
ecs::{
assets::ResourceManager, ecs::{
query::{Res, View},
system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem},
World,
},
game::App,
input::{
}, game::App, gltf::Gltf, input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
},
math::{self, Transform, Vec3},
render::light::directional::DirectionalLight,
scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
},
DeltaTime,
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{
CameraBundle, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, 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 tracing::info;
@ -135,9 +126,11 @@ fn setup_scene_plugin(app: &mut App) {
));
}
let mut camera = CameraComponent::new_3d();
camera.transform.translation += math::Vec3::new(0.0, 0.0, 1.5);
world.spawn((camera, FreeFlyCamera::default()));
world.spawn((
CameraBundle::default(),
Transform::from_xyz(0.0, 0.0, 1.5),
FreeFlyCamera::default(),
));
let fps_counter = |counter: Res<fps_counter::FPSCounter>,
delta: Res<DeltaTime>| -> anyhow::Result<()> {

View File

@ -1,9 +1,9 @@
use lyra_engine::{
assets::ResourceManager, gltf::Gltf, game::App, input::{
assets::ResourceManager, game::App, gltf::Gltf, input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{
self, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN
self, CameraBundle, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN
}, script::{lua::{LuaScript, LuaScriptingPlugin}, Script, ScriptList}
};
@ -125,9 +125,11 @@ fn setup_scene_plugin(app: &mut App) {
));
}
let mut camera = CameraComponent::new_3d();
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
world.spawn((camera, FreeFlyCamera::default()));
world.spawn((
CameraBundle::default(),
Transform::from_xyz(0.0, 0.0, 5.5),
FreeFlyCamera::default(),
));
}
fn setup_script_plugin(app: &mut App) {

View File

@ -1,4 +1,4 @@
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 lyra_engine::{assets::ResourceManager, ecs::query::Res, game::App, gltf::Gltf, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::light::{directional::DirectionalLight, PointLight}, scene::{CameraBundle, 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;
@ -95,7 +95,6 @@ async fn main() {
app.with_plugin(lyra_engine::DefaultPlugins);
app.with_plugin(setup_scene_plugin);
app.with_plugin(action_handler_plugin);
app.with_plugin(camera_debug_plugin);
app.with_plugin(FreeFlyCameraPlugin);
app.run();
}
@ -184,26 +183,15 @@ fn setup_scene_plugin(app: &mut App) {
));
}
let mut camera = CameraComponent::new_3d();
// these values were taken by manually positioning the camera in the scene.
camera.transform = Transform::new(
world.spawn((
CameraBundle::default(),
// these values were taken by manually positioning the camera in the scene
// and printing the position to console.
Transform::new(
Vec3::new(-10.0, 0.94, -0.28),
Quat::from_xyzw(0.03375484, -0.7116095, 0.0342693, 0.70092666),
Vec3::ONE
);
world.spawn(( camera, FreeFlyCamera::default() ));
}
fn camera_debug_plugin(app: &mut App) {
let sys = |handler: Res<ActionHandler>, view: View<&mut CameraComponent>| -> anyhow::Result<()> {
if let Some(true) = handler.was_action_just_pressed("Debug") {
for mut cam in view.into_iter() {
cam.debug = !cam.debug;
}
}
Ok(())
};
app.with_system("camera_debug_trigger", sys, &[]);
Vec3::ONE,
),
FreeFlyCamera::default(),
));
}

View File

@ -1,6 +1,7 @@
use lyra_engine::{
assets::ResourceManager, gltf::Gltf,
assets::ResourceManager,
game::App,
gltf::Gltf,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
@ -11,9 +12,9 @@ use lyra_engine::{
light::{directional::DirectionalLight, SpotLight},
},
scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
CameraBundle, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT,
ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD,
ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
},
};
@ -115,13 +116,6 @@ fn setup_scene_plugin(app: &mut App) {
Transform::from_xyz(0.0, -5.0, -2.0),
)); */
let cube_gltf = resman
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
.unwrap();
cube_gltf.wait_recurse_dependencies_load();
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
let palm_tree_platform_gltf = resman
.request::<Gltf>("../assets/shadows-platform-palmtree.glb")
.unwrap();
@ -214,8 +208,12 @@ fn setup_scene_plugin(app: &mut App) {
Vec3::new(4.0 - 1.43, -13.0, 0.0),
//Vec3::new(-5.0, 1.0, -0.28),
//Vec3::new(-10.0, 0.94, -0.28),
Quat::from_euler(math::EulerRot::XYZ, 0.0, math::Angle::Degrees(-45.0).to_radians(), 0.0),
Quat::from_euler(
math::EulerRot::XYZ,
0.0,
math::Angle::Degrees(-45.0).to_radians(),
0.0,
),
Vec3::new(0.15, 0.15, 0.15),
);
@ -238,10 +236,8 @@ fn setup_scene_plugin(app: &mut App) {
));
}
let mut camera = CameraComponent::new_3d();
camera.transform.translation = math::Vec3::new(-1.0, -10.0, -1.5);
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
camera.transform.rotate_y(math::Angle::Degrees(-90.0));
world.spawn((camera, FreeFlyCamera::default()));
let mut pos = Transform::from_xyz(-1.0, -10.0, -1.5);
pos.rotate_x(math::Angle::Degrees(-27.0));
pos.rotate_y(math::Angle::Degrees(-90.0));
world.spawn((CameraBundle::default(), pos, FreeFlyCamera::default()));
}

View File

@ -1,10 +1,18 @@
use lyra_engine::{
assets::ResourceManager, gltf::Gltf, ecs::query::View, game::App, input::{
assets::ResourceManager,
game::App,
gltf::Gltf,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId,
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{
system_update_world_transforms, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN
}, winit::WindowOptions
},
math::{self, Transform, Vec3},
render::light::directional::DirectionalLight,
scene::{
system_update_world_transforms, CameraBundle, FreeFlyCamera, FreeFlyCameraPlugin,
WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
},
};
#[async_std::main]
@ -84,7 +92,11 @@ async fn main() {
.with_plugin(action_handler_plugin)
//.with_plugin(camera_debug_plugin)
.with_plugin(FreeFlyCameraPlugin)
.with_system("system_update_world_transforms", system_update_world_transforms, &[]);
.with_system(
"system_update_world_transforms",
system_update_world_transforms,
&[],
);
a.run();
}
@ -135,7 +147,9 @@ fn setup_scene_plugin(app: &mut App) {
));
}
let mut camera = CameraComponent::new_3d();
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
world.spawn((camera, FreeFlyCamera::default()));
world.spawn((
CameraBundle::default(),
Transform::from_xyz(0.0, 0.0, 5.5),
FreeFlyCamera::default(),
));
}

View File

@ -1,14 +1,14 @@
use std::ptr::NonNull;
use lyra_engine::assets::ResourceManager;
use lyra_engine::scene::CameraBundle;
use lyra_engine::{
gltf::Gltf,
ecs::{
query::{Res, View},
system::{Criteria, CriteriaSchedule, IntoSystem},
system::{Criteria, CriteriaSchedule},
Component, World,
},
game::App,
gltf::Gltf,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
@ -16,9 +16,9 @@ use lyra_engine::{
math::{self, Quat, Transform, Vec3},
render::light::{directional::DirectionalLight, PointLight, SpotLight},
scene::{
self, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
self, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT,
ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD,
ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
},
DeltaTime,
};
@ -101,7 +101,109 @@ struct CubeFlag;
#[async_std::main]
async fn main() {
let setup_sys = |world: &mut World| -> anyhow::Result<()> {
let action_handler_plugin = |app: &mut App| {
let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0))
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
.add_action("Debug", Action::new(ActionKind::Button))
.add_mapping(
ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
.bind(
ACTLBL_MOVE_FORWARD_BACKWARD,
&[
ActionSource::Keyboard(KeyCode::KeyW).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::KeyS).into_binding_modifier(-1.0),
],
)
.bind(
ACTLBL_MOVE_LEFT_RIGHT,
&[
ActionSource::Keyboard(KeyCode::KeyA).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::KeyD).into_binding_modifier(1.0),
],
)
.bind(
ACTLBL_MOVE_UP_DOWN,
&[
ActionSource::Keyboard(KeyCode::KeyC).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::KeyZ).into_binding_modifier(-1.0),
],
)
.bind(
ACTLBL_LOOK_LEFT_RIGHT,
&[
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
ActionSource::Keyboard(KeyCode::ArrowLeft).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0),
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickX)).into_binding(),
],
)
.bind(
ACTLBL_LOOK_UP_DOWN,
&[
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
ActionSource::Keyboard(KeyCode::ArrowUp).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowDown).into_binding_modifier(1.0),
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickY)).into_binding(),
],
)
.bind(
ACTLBL_LOOK_ROLL,
&[
ActionSource::Keyboard(KeyCode::KeyE).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::KeyQ).into_binding_modifier(1.0),
],
)
.bind(
"Debug",
&[ActionSource::Keyboard(KeyCode::KeyB).into_binding()],
)
.finish(),
)
.finish();
let world = &mut app.world;
world.add_resource(action_handler);
app.with_plugin(InputActionPlugin);
};
/* let script_test_plugin = |app: &mut App| {
game.with_plugin(LuaScriptingPlugin);
let world = game.world_mut();
let mut res_man = world.get_resource_mut::<ResourceManager>();
let script = res_man.request::<LuaScript>("scripts/test.lua").unwrap();
res_man.watch("scripts/test.lua", false).unwrap();
drop(res_man);
let script = Script::new("test.lua", script);
let scripts = ScriptList::new(vec![script]);
world.spawn((scripts,));
}; */
let mut app = App::new();
app.with_plugin(lyra_engine::DefaultPlugins);
app.with_plugin(setup_scene_plugin);
app.with_plugin(action_handler_plugin);
//app.with_plugin(script_test_plugin)
//app.with_plugin(fps_plugin)
app.with_system(
"update_world_transforms",
scene::system_update_world_transforms,
&[],
);
app.with_plugin(FreeFlyCameraPlugin);
app.run();
}
fn setup_scene_plugin(app: &mut App) {
let world = &mut app.world;
{
/* let mut window_options = world.get_resource_mut::<Ct<WindowOptions>>();
window_options.cursor_grab = CursorGrabMode::Confined;
@ -267,132 +369,17 @@ async fn main() {
));
} */
let mut camera = CameraComponent::new_3d();
// these values were taken by manually positioning the camera in the scene.
camera.transform = Transform::new(
world.spawn((
CameraBundle::default(),
// these values were taken by manually positioning the camera in the scene
// and printing the position to console.
Transform::new(
Vec3::new(-10.0, 0.94, -0.28),
Quat::from_xyzw(0.03375484, -0.7116095, 0.0342693, 0.70092666),
Vec3::ONE,
);
//camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
world.spawn((camera, FreeFlyCamera::default()));
),
FreeFlyCamera::default(),
));
Ok(())
};
let camera_debug_plugin = move |app: &mut App| {
let sys =
|handler: Res<ActionHandler>, view: View<&mut CameraComponent>| -> anyhow::Result<()> {
if let Some(true) = handler.was_action_just_pressed("Debug") {
for mut cam in view.into_iter() {
cam.debug = !cam.debug;
}
}
Ok(())
};
app.with_system("camera_debug_trigger", sys, &[]);
app.with_system(
"update_world_transforms",
scene::system_update_world_transforms,
&[],
);
};
let action_handler_plugin = |app: &mut App| {
let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0))
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
.add_action("Debug", Action::new(ActionKind::Button))
.add_mapping(
ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
.bind(
ACTLBL_MOVE_FORWARD_BACKWARD,
&[
ActionSource::Keyboard(KeyCode::KeyW).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::KeyS).into_binding_modifier(-1.0),
],
)
.bind(
ACTLBL_MOVE_LEFT_RIGHT,
&[
ActionSource::Keyboard(KeyCode::KeyA).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::KeyD).into_binding_modifier(1.0),
],
)
.bind(
ACTLBL_MOVE_UP_DOWN,
&[
ActionSource::Keyboard(KeyCode::KeyC).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::KeyZ).into_binding_modifier(-1.0),
],
)
.bind(
ACTLBL_LOOK_LEFT_RIGHT,
&[
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
ActionSource::Keyboard(KeyCode::ArrowLeft).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0),
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickX)).into_binding(),
],
)
.bind(
ACTLBL_LOOK_UP_DOWN,
&[
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
ActionSource::Keyboard(KeyCode::ArrowUp).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowDown).into_binding_modifier(1.0),
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickY)).into_binding(),
],
)
.bind(
ACTLBL_LOOK_ROLL,
&[
ActionSource::Keyboard(KeyCode::KeyE).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::KeyQ).into_binding_modifier(1.0),
],
)
.bind(
"Debug",
&[ActionSource::Keyboard(KeyCode::KeyB).into_binding()],
)
.finish(),
)
.finish();
let world = &mut app.world;
world.add_resource(action_handler);
app.with_plugin(InputActionPlugin);
};
/* let script_test_plugin = |app: &mut App| {
game.with_plugin(LuaScriptingPlugin);
let world = game.world_mut();
let mut res_man = world.get_resource_mut::<ResourceManager>();
let script = res_man.request::<LuaScript>("scripts/test.lua").unwrap();
res_man.watch("scripts/test.lua", false).unwrap();
drop(res_man);
let script = Script::new("test.lua", script);
let scripts = ScriptList::new(vec![script]);
world.spawn((scripts,));
}; */
let mut app = App::new();
app.with_plugin(lyra_engine::DefaultPlugins);
app.with_startup_system(setup_sys.into_system());
app.with_plugin(action_handler_plugin);
//app.with_plugin(script_test_plugin)
//app.with_plugin(fps_plugin)
app.with_plugin(camera_debug_plugin);
app.with_plugin(FreeFlyCameraPlugin);
app.run();
//Ok(())
}