Compare commits
13 Commits
main
...
feat/rende
Author | SHA1 | Date |
---|---|---|
SeanOMik | d1aee610cc | |
SeanOMik | b9d0398157 | |
SeanOMik | cb4cf9a48f | |
SeanOMik | 558f027b19 | |
SeanOMik | 4018fdaa80 | |
SeanOMik | b8003795fa | |
SeanOMik | ad4de0b5be | |
SeanOMik | 503ea5b450 | |
SeanOMik | 95b01613fe | |
SeanOMik | 8d54c42d74 | |
SeanOMik | 3a4333d16e | |
SeanOMik | 617c4d69e8 | |
SeanOMik | 315924f920 |
|
@ -4,6 +4,24 @@
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"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",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
|
@ -515,9 +515,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.18.0"
|
version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
|
checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck_derive",
|
"bytemuck_derive",
|
||||||
]
|
]
|
||||||
|
@ -1893,6 +1893,7 @@ dependencies = [
|
||||||
name = "lyra-math"
|
name = "lyra-math"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
"glam",
|
"glam",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse_macro_input, DeriveInput};
|
use syn::{parse_macro_input, spanned::Spanned, DeriveInput};
|
||||||
|
|
||||||
#[proc_macro_derive(Component)]
|
#[proc_macro_derive(Component)]
|
||||||
pub fn derive_component(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -348,7 +348,7 @@ impl Archetype {
|
||||||
self.entity_ids.insert(entity, entity_index);
|
self.entity_ids.insert(entity, entity_index);
|
||||||
self.entities.push(entity);
|
self.entities.push(entity);
|
||||||
|
|
||||||
bundle.take(|data, type_id, info| {
|
bundle.take(&mut |data, type_id, info| {
|
||||||
self.put_component_at(
|
self.put_component_at(
|
||||||
tick,
|
tick,
|
||||||
data,
|
data,
|
||||||
|
@ -621,7 +621,7 @@ impl Archetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (eid, bundle) in new_columns.into_iter().enumerate() {
|
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();
|
let col = self.get_column_mut(tyid).unwrap();
|
||||||
col.insert_entity(eid, ptr, tick.clone());
|
col.insert_entity(eid, ptr, tick.clone());
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub trait Bundle {
|
||||||
|
|
||||||
/// Take the bundle by calling the closure with pointers to each component, its type and size.
|
/// 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.
|
/// 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`]
|
/// Returns a boolean indicating if this Bundle is dynamic. See [`DynamicBundle`]
|
||||||
fn is_dynamic(&self) -> bool;
|
fn is_dynamic(&self) -> bool;
|
||||||
|
@ -26,7 +26,7 @@ impl Bundle for () {
|
||||||
vec![ComponentInfo::new::<()>()]
|
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::<()>());
|
f(NonNull::from(&self).cast(), DynTypeId::of::<()>(), ComponentInfo::new::<()>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ impl<C: Component> Bundle for C {
|
||||||
vec![ComponentInfo::new::<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>());
|
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
|
// 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 {
|
macro_rules! impl_bundle_tuple {
|
||||||
( $($name: ident),+ ) => (
|
( $($name: ident),+ ) => (
|
||||||
|
// these names wont follow rust convention, but its a macro so deal with it
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
impl<$($name: Component),+> Bundle for ($($name,)+) {
|
impl<$($name: Bundle),+> Bundle for ($($name,)+) {
|
||||||
|
#[inline(always)]
|
||||||
fn type_ids(&self) -> Vec<DynTypeId> {
|
fn type_ids(&self) -> Vec<DynTypeId> {
|
||||||
// these names wont follow rust convention, but its a macro so deal with it
|
let ($($name,)+) = self;
|
||||||
vec![$(DynTypeId::of::<$name>()),+]
|
|
||||||
|
let mut v = vec![];
|
||||||
|
$(
|
||||||
|
v.extend($name.type_ids().into_iter());
|
||||||
|
)+
|
||||||
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn info(&self) -> Vec<ComponentInfo> {
|
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)) {
|
#[inline(always)]
|
||||||
// these names wont follow rust convention, but its a macro so deal with it
|
fn take(self, f: &mut impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
|
||||||
let ($($name,)+) = self;
|
let ($($name,)+) = self;
|
||||||
|
|
||||||
$(
|
$(
|
||||||
f(NonNull::from(&$name).cast(), DynTypeId::of::<$name>(), ComponentInfo::new::<$name>());
|
$name.take(f);
|
||||||
// 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);
|
|
||||||
)+
|
)+
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn is_dynamic(&self) -> bool {
|
fn is_dynamic(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -166,7 +178,7 @@ impl DynamicBundle {
|
||||||
where
|
where
|
||||||
B: Bundle
|
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
|
// 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
|
// `bundle` lasts for as long as the `DynamicBundle`. If the data wasn't copied, the pointers
|
||||||
// could be invalid later.
|
// could be invalid later.
|
||||||
|
@ -192,7 +204,7 @@ impl Bundle for DynamicBundle {
|
||||||
self.bundle.iter().map(|b| b.1).collect()
|
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() {
|
for (data, info) in self.bundle.into_iter() {
|
||||||
f(data, info.type_id(), info);
|
f(data, info.type_id(), info);
|
||||||
}
|
}
|
||||||
|
@ -202,3 +214,70 @@ impl Bundle for DynamicBundle {
|
||||||
true
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -166,6 +166,15 @@ impl<'a, 'b> Commands<'a, 'b> {
|
||||||
e
|
e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert or update existing components into an Entity.
|
||||||
|
///
|
||||||
|
/// See [`World::insert`].
|
||||||
|
pub fn insert<B: Bundle + 'static>(&mut self, entity: Entity, bundle: B) {
|
||||||
|
self.add(move | world: &mut World| {
|
||||||
|
world.insert(entity, bundle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute all commands in the queue, in order of insertion
|
/// Execute all commands in the queue, in order of insertion
|
||||||
pub fn execute(&mut self, world: &mut World) {
|
pub fn execute(&mut self, world: &mut World) {
|
||||||
self.queue.execute(Some(world));
|
self.queue.execute(Some(world));
|
||||||
|
|
|
@ -42,20 +42,13 @@ impl DynTypeId {
|
||||||
*self == id
|
*self == id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force self into a rust TypeId, will panic if this type is not a Rust type.
|
/// Force self into a rust TypeId, will panic if this type is not a Rust type.
|
||||||
pub fn as_rust(&self) -> TypeId {
|
pub fn as_rust(&self) -> TypeId {
|
||||||
match self {
|
match self {
|
||||||
DynTypeId::Rust(t) => *t,
|
DynTypeId::Rust(t) => *t,
|
||||||
DynTypeId::Unknown(_) => panic!("This type is unknown to rust, cannot construct a TypeId from it!"),
|
DynTypeId::Unknown(_) => panic!("This type is unknown to rust, cannot construct a TypeId from it!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_unknown(&self) -> Option<u128> {
|
|
||||||
match self {
|
|
||||||
DynTypeId::Rust(_) => None,
|
|
||||||
DynTypeId::Unknown(id) => Some(*id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Some information about a component.
|
/// Some information about a component.
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
mod has;
|
mod has;
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
pub use has::*;
|
pub use has::*;
|
||||||
|
|
||||||
mod or;
|
mod or;
|
||||||
|
|
|
@ -112,7 +112,7 @@ impl<'a, T: ResourceObject> Res<'a, T> {
|
||||||
|
|
||||||
/// Returns a boolean indicating if the resource changed.
|
/// Returns a boolean indicating if the resource changed.
|
||||||
pub fn changed(&self) -> bool {
|
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
|
/// 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 {
|
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
|
/// The tick that this resource was last modified at
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{any::{Any, TypeId}, sync::Arc};
|
||||||
|
|
||||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
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.
|
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
|
||||||
pub trait ResourceObject: Send + Sync + Any {
|
pub trait ResourceObject: Send + Sync + Any {
|
||||||
|
@ -25,6 +25,14 @@ pub struct TrackedResource<T: ?Sized> {
|
||||||
pub res: T,
|
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.
|
/// A type erased storage for a Resource.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ResourceData {
|
pub struct ResourceData {
|
||||||
|
@ -89,6 +97,6 @@ impl ResourceData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn changed(&self, tick: Tick) -> bool {
|
pub fn changed(&self, tick: Tick) -> bool {
|
||||||
*self.data.borrow().tick >= *tick - 1
|
self.data.borrow().changed(tick)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -180,7 +180,7 @@ impl World {
|
||||||
let entry_idx = *current_arch.entity_indexes()
|
let entry_idx = *current_arch.entity_indexes()
|
||||||
.get(&entity).unwrap();
|
.get(&entity).unwrap();
|
||||||
|
|
||||||
bundle.take(|ptr, id, _info| {
|
bundle.take(&mut |ptr, id, _info| {
|
||||||
let col = current_arch.get_column_mut(id).unwrap();
|
let col = current_arch.get_column_mut(id).unwrap();
|
||||||
unsafe { col.set_at(entry_idx.0 as _, ptr, tick) };
|
unsafe { col.set_at(entry_idx.0 as _, ptr, tick) };
|
||||||
});
|
});
|
||||||
|
@ -459,6 +459,8 @@ impl World {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a resource from the 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>> {
|
pub fn get_resource<T: ResourceObject>(&self) -> Option<Res<T>> {
|
||||||
self.get_tracked_resource::<T>().map(|r| Res {
|
self.get_tracked_resource::<T>().map(|r| Res {
|
||||||
inner: r,
|
inner: r,
|
||||||
|
@ -476,20 +478,24 @@ impl World {
|
||||||
|
|
||||||
/// Gets a reference to a change tracked resource.
|
/// 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
|
/// You will have to manually downcast the inner resource. Most people don't need this, see
|
||||||
/// [`World::get_resource`].
|
/// [`World::get_resource`].
|
||||||
pub fn get_tracked_resource<T: ResourceObject>(&self) -> Option<AtomicRef<TrackedResource<dyn ResourceObject>>> {
|
pub fn get_tracked_resource<T: ResourceObject>(&self) -> Option<AtomicRef<TrackedResource<dyn ResourceObject>>> {
|
||||||
self.resources.get(&TypeId::of::<T>())
|
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.
|
/// 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
|
/// You will have to manually downcast the inner resource. Most people don't need this, see
|
||||||
/// [`World::get_resource_mut`].
|
/// [`World::get_resource_mut`].
|
||||||
pub fn get_tracked_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<TrackedResource<dyn ResourceObject>>> {
|
pub fn get_tracked_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<TrackedResource<dyn ResourceObject>>> {
|
||||||
self.resources.get(&TypeId::of::<T>())
|
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.
|
/// Returns a boolean indicating if the resource changed.
|
||||||
|
@ -791,14 +797,18 @@ mod tests {
|
||||||
world.add_resource(SimpleCounter(50));
|
world.add_resource(SimpleCounter(50));
|
||||||
|
|
||||||
assert!(world.has_resource_changed::<SimpleCounter>());
|
assert!(world.has_resource_changed::<SimpleCounter>());
|
||||||
|
world.tick();
|
||||||
|
|
||||||
world.spawn(Vec2::new(50.0, 50.0));
|
world.spawn(Vec2::new(50.0, 50.0));
|
||||||
|
world.tick();
|
||||||
|
|
||||||
assert!(!world.has_resource_changed::<SimpleCounter>());
|
assert!(!world.has_resource_changed::<SimpleCounter>());
|
||||||
|
world.tick();
|
||||||
|
|
||||||
let mut counter = world.get_resource_mut::<SimpleCounter>()
|
let mut counter = world.get_resource_mut::<SimpleCounter>()
|
||||||
.expect("Counter resource is missing");
|
.expect("Counter resource is missing");
|
||||||
counter.0 += 100;
|
counter.0 += 100;
|
||||||
|
drop(counter);
|
||||||
|
|
||||||
assert!(world.has_resource_changed::<SimpleCounter>());
|
assert!(world.has_resource_changed::<SimpleCounter>());
|
||||||
}
|
}
|
||||||
|
|
|
@ -493,7 +493,7 @@ impl ActionHandlerBuilder {
|
||||||
//fn actions_system(world: &mut World) -> anyhow::Result<()> {
|
//fn actions_system(world: &mut World) -> anyhow::Result<()> {
|
||||||
fn actions_system(
|
fn actions_system(
|
||||||
input_btns: Res<InputButtons<KeyCode>>,
|
input_btns: Res<InputButtons<KeyCode>>,
|
||||||
mut mouse_ev: EventReader<MouseMotion>,
|
mouse_ev: EventReader<MouseMotion>,
|
||||||
mut handler: ResMut<ActionHandler>,
|
mut handler: ResMut<ActionHandler>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// clear the states of all axises each frame
|
// clear the states of all axises each frame
|
||||||
|
|
|
@ -4,6 +4,7 @@ use lyra_gltf::GltfLoader;
|
||||||
use lyra_resource::ResourceManager;
|
use lyra_resource::ResourceManager;
|
||||||
|
|
||||||
use crate::game::App;
|
use crate::game::App;
|
||||||
|
use crate::render::PreprocessShaderLoader;
|
||||||
use crate::winit::{WinitPlugin, WindowPlugin};
|
use crate::winit::{WinitPlugin, WindowPlugin};
|
||||||
use crate::DeltaTimePlugin;
|
use crate::DeltaTimePlugin;
|
||||||
use crate::input::InputPlugin;
|
use crate::input::InputPlugin;
|
||||||
|
@ -101,7 +102,10 @@ pub struct ResourceManagerPlugin;
|
||||||
|
|
||||||
impl Plugin for ResourceManagerPlugin {
|
impl Plugin for ResourceManagerPlugin {
|
||||||
fn setup(&mut self, app: &mut App) {
|
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 {
|
impl Plugin for DefaultPlugins {
|
||||||
fn setup(&mut self, app: &mut App) {
|
fn setup(&mut self, app: &mut App) {
|
||||||
WinitPlugin::default().setup(app);
|
|
||||||
CommandQueuePlugin.setup(app);
|
|
||||||
InputPlugin.setup(app);
|
|
||||||
ResourceManagerPlugin.setup(app);
|
ResourceManagerPlugin.setup(app);
|
||||||
GltfPlugin.setup(app);
|
GltfPlugin.setup(app);
|
||||||
|
WinitPlugin::default().setup(app);
|
||||||
WindowPlugin::default().setup(app);
|
WindowPlugin::default().setup(app);
|
||||||
|
CommandQueuePlugin.setup(app);
|
||||||
|
InputPlugin.setup(app);
|
||||||
DeltaTimePlugin.setup(app);
|
DeltaTimePlugin.setup(app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,7 @@
|
||||||
use lyra_reflect::Reflect;
|
use glam::Vec2;
|
||||||
use winit::dpi::PhysicalSize;
|
use lyra_math::Transform;
|
||||||
|
|
||||||
use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX}, scene::CameraComponent};
|
use crate::scene::CameraProjection;
|
||||||
|
|
||||||
#[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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
@ -48,9 +15,7 @@ pub struct CameraUniform {
|
||||||
pub projection: glam::Mat4,
|
pub projection: glam::Mat4,
|
||||||
/// The position of the camera
|
/// The position of the camera
|
||||||
pub position: glam::Vec3,
|
pub position: glam::Vec3,
|
||||||
pub tile_debug: u32,
|
_padding: u32,
|
||||||
//_padding: [u8; 3],
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CameraUniform {
|
impl Default for CameraUniform {
|
||||||
|
@ -61,106 +26,45 @@ impl Default for CameraUniform {
|
||||||
view_projection: glam::Mat4::IDENTITY,
|
view_projection: glam::Mat4::IDENTITY,
|
||||||
projection: glam::Mat4::IDENTITY,
|
projection: glam::Mat4::IDENTITY,
|
||||||
position: Default::default(),
|
position: Default::default(),
|
||||||
tile_debug: 0,
|
_padding: 0,
|
||||||
//_padding: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraUniform {
|
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 {
|
Self {
|
||||||
view,
|
view,
|
||||||
inverse_projection,
|
inverse_projection,
|
||||||
view_projection,
|
view_projection,
|
||||||
projection,
|
projection,
|
||||||
position,
|
position,
|
||||||
tile_debug: 0
|
_padding: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub fn from_component(transform: Transform, projection: CameraProjection, viewport_size: Vec2) -> Self {
|
||||||
pub struct RenderCamera {
|
let position = transform.translation;
|
||||||
view_proj: glam::Mat4,
|
let forward = transform.forward();
|
||||||
|
let up = transform.up();
|
||||||
|
let view = glam::Mat4::look_to_rh(position, forward, up);
|
||||||
|
|
||||||
#[allow(dead_code)]
|
let projection = projection.to_mat4(viewport_size);
|
||||||
size: PhysicalSize<u32>,
|
let view_projection = projection * view;
|
||||||
aspect: f32,
|
|
||||||
znear: f32,
|
|
||||||
zfar: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderCamera {
|
|
||||||
pub fn new(size: PhysicalSize<u32>) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
view_proj: glam::Mat4::IDENTITY,
|
view,
|
||||||
|
inverse_projection: projection.inverse(),
|
||||||
size,
|
view_projection,
|
||||||
aspect: size.width as f32 / size.height as f32,
|
projection,
|
||||||
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,
|
position,
|
||||||
forward,
|
_padding: 0,
|
||||||
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,
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use lyra_ecs::World;
|
use lyra_ecs::World;
|
||||||
|
use lyra_resource::{RequestError, ResHandle, ResourceManager};
|
||||||
pub use node::*;
|
pub use node::*;
|
||||||
|
|
||||||
mod passes;
|
mod passes;
|
||||||
|
@ -20,9 +21,9 @@ pub use render_target::*;
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use tracing::{debug_span, instrument, trace, warn};
|
use tracing::{debug_span, instrument, trace, warn};
|
||||||
use wgpu::CommandEncoder;
|
use wgpu::{util::DeviceExt, BufferUsages, 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`].
|
/// A trait that represents the label of a resource, slot, or node in the [`RenderGraph`].
|
||||||
pub trait RenderGraphLabel: Debug + 'static {
|
pub trait RenderGraphLabel: Debug + 'static {
|
||||||
|
@ -118,11 +119,15 @@ pub struct RenderGraph {
|
||||||
/// A directed graph used to determine dependencies of nodes.
|
/// A directed graph used to determine dependencies of nodes.
|
||||||
node_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
|
node_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
|
||||||
view_target: Rc<RefCell<ViewTarget>>,
|
view_target: Rc<RefCell<ViewTarget>>,
|
||||||
shader_prepoc: wgsl_preprocessor::Processor,
|
/// Type erased data of ResourceManager ecs resource
|
||||||
|
resource_manager: lyra_ecs::ResourceData,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderGraph {
|
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 {
|
Self {
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
|
@ -132,7 +137,7 @@ impl RenderGraph {
|
||||||
bind_groups: Default::default(),
|
bind_groups: Default::default(),
|
||||||
node_graph: Default::default(),
|
node_graph: Default::default(),
|
||||||
view_target,
|
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.
|
/// 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.
|
/// 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> {
|
pub fn register_shader(&mut self, shader_src: &str) -> Result<Option<String>, wgsl_preprocessor::Error> {
|
||||||
self.shader_prepoc.parse_module(shader_src)
|
self.shader_prepoc.parse_module(shader_src)
|
||||||
}
|
}
|
||||||
|
@ -527,6 +532,25 @@ impl RenderGraph {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> {
|
pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> {
|
||||||
self.shader_prepoc.preprocess_module(shader_path)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a buffer with a single item inside of it
|
||||||
|
pub fn create_buffer_with_data<T: bytemuck::NoUninit>(&self, label: Option<&'static str>, usage: BufferUsages, data: &T) -> wgpu::Buffer {
|
||||||
|
self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label,
|
||||||
|
usage,
|
||||||
|
contents: bytemuck::bytes_of(data),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,17 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use glam::UVec2;
|
use glam::UVec2;
|
||||||
use lyra_game_derive::RenderGraphLabel;
|
use lyra_game_derive::RenderGraphLabel;
|
||||||
|
use lyra_math::Transform;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use winit::dpi::PhysicalSize;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
render::{
|
render::{
|
||||||
camera::{CameraUniform, RenderCamera},
|
camera::CameraUniform,
|
||||||
graph::{
|
graph::{
|
||||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||||
},
|
},
|
||||||
render_buffer::BufferWrapper, texture::RenderTexture,
|
render_buffer::BufferWrapper, texture::RenderTexture,
|
||||||
},
|
}, scene::{Camera, CameraProjection},
|
||||||
scene::CameraComponent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
|
#[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) {
|
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;
|
||||||
let screen_size = graph.view_target().size();
|
|
||||||
|
|
||||||
let mut render_cam =
|
for (camera, projection, transform) in world.view_iter::<(&Camera, &CameraProjection, &Transform)>() {
|
||||||
RenderCamera::new(PhysicalSize::new(screen_size.x, screen_size.y));
|
if camera.is_active {
|
||||||
let uniform = render_cam.calc_view_projection(&camera);
|
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);
|
||||||
|
|
||||||
context.queue_buffer_write_with(BasePassSlots::Camera, 0, uniform)
|
found_camera = true;
|
||||||
} else {
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_camera {
|
||||||
warn!("Missing camera!");
|
warn!("Missing camera!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{collections::HashMap, rc::Rc, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use lyra_game_derive::RenderGraphLabel;
|
use lyra_game_derive::RenderGraphLabel;
|
||||||
|
|
||||||
use crate::render::{
|
use crate::render::{
|
||||||
graph::{Node, NodeDesc, NodeType},
|
graph::{Node, NodeDesc, NodeType},
|
||||||
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
|
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, VertexState},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||||
|
@ -64,10 +64,8 @@ impl Node for FxaaPass {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let shader = Rc::new(Shader {
|
let shader = graph.load_shader_str("fxaa_shader", include_str!("../../shaders/fxaa.wgsl"))
|
||||||
label: Some("fxaa_shader".into()),
|
.expect("failed to load wgsl shader from manager");
|
||||||
source: include_str!("../../shaders/fxaa.wgsl").to_string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let vt = graph.view_target();
|
let vt = graph.view_target();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{mem, rc::Rc, sync::Arc};
|
use std::{mem, sync::Arc};
|
||||||
|
|
||||||
use glam::Vec2Swizzles;
|
use glam::Vec2Swizzles;
|
||||||
use lyra_ecs::World;
|
use lyra_ecs::World;
|
||||||
|
@ -8,7 +8,7 @@ use wgpu::util::DeviceExt;
|
||||||
use crate::render::{
|
use crate::render::{
|
||||||
graph::{
|
graph::{
|
||||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||||
}, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor, Shader}
|
}, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor}
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{BasePassSlots, LightBasePassSlots};
|
use super::{BasePassSlots, LightBasePassSlots};
|
||||||
|
@ -219,10 +219,8 @@ impl Node for LightCullComputePass {
|
||||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
|
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
|
||||||
let light_indices_bg_layout = graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
|
let light_indices_bg_layout = graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||||
|
|
||||||
let shader = Rc::new(Shader {
|
let shader = graph.load_shader_str("light_cull_comp_shader", include_str!("../../shaders/light_cull.comp.wgsl"))
|
||||||
label: Some("light_cull_comp_shader".into()),
|
.expect("failed to load light cull compute shader");
|
||||||
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let pipeline = ComputePipeline::create(device, &ComputePipelineDescriptor {
|
let pipeline = ComputePipeline::create(device, &ComputePipelineDescriptor {
|
||||||
label: Some("light_cull_pipeline".into()),
|
label: Some("light_cull_pipeline".into()),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{rc::Rc, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use lyra_ecs::{AtomicRef, ResourceData};
|
use lyra_ecs::{AtomicRef, ResourceData};
|
||||||
use lyra_game_derive::RenderGraphLabel;
|
use lyra_game_derive::RenderGraphLabel;
|
||||||
|
@ -7,7 +7,7 @@ use tracing::{instrument, warn};
|
||||||
use crate::render::{
|
use crate::render::{
|
||||||
desc_buf_lay::DescVertexBufferLayout,
|
desc_buf_lay::DescVertexBufferLayout,
|
||||||
graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext},
|
graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext},
|
||||||
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
|
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
||||||
texture::RenderTexture,
|
texture::RenderTexture,
|
||||||
transform_buffer_storage::TransformBuffers,
|
transform_buffer_storage::TransformBuffers,
|
||||||
vertex::Vertex,
|
vertex::Vertex,
|
||||||
|
@ -102,10 +102,8 @@ impl Node for MeshPass {
|
||||||
_: &mut RenderGraphContext,
|
_: &mut RenderGraphContext,
|
||||||
) {
|
) {
|
||||||
if self.pipeline.is_none() {
|
if self.pipeline.is_none() {
|
||||||
let shader_mod = graph.register_shader(include_str!("../../shaders/base.wgsl"))
|
let shader = graph.load_shader_str("mesh_base", include_str!("../../shaders/base.wgsl"))
|
||||||
.expect("failed to register shader").expect("base shader missing module");
|
.expect("failed to load base mesh shader");
|
||||||
let shader_src = graph.preprocess_shader(&shader_mod)
|
|
||||||
.expect("failed to preprocess shader");
|
|
||||||
|
|
||||||
let device = graph.device();
|
let device = graph.device();
|
||||||
let surface_config_format = graph.view_target().format();
|
let surface_config_format = graph.view_target().format();
|
||||||
|
@ -299,11 +297,6 @@ impl Node for MeshPass {
|
||||||
graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
|
graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||||
let atlas_bgl = self.shadows_atlas.as_ref().unwrap().layout.clone();
|
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
|
let transforms = world
|
||||||
.get_resource_data::<TransformBuffers>()
|
.get_resource_data::<TransformBuffers>()
|
||||||
.expect("Missing transform buffers");
|
.expect("Missing transform buffers");
|
||||||
|
@ -414,12 +407,12 @@ impl Node for MeshPass {
|
||||||
view,
|
view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
load: wgpu::LoadOp::Load,/* wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
r: 0.1,
|
r: 0.1,
|
||||||
g: 0.2,
|
g: 0.2,
|
||||||
b: 0.3,
|
b: 0.3,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
}),
|
}), */
|
||||||
store: wgpu::StoreOp::Store,
|
store: wgpu::StoreOp::Store,
|
||||||
},
|
},
|
||||||
})],
|
})],
|
||||||
|
@ -441,9 +434,9 @@ impl Node for MeshPass {
|
||||||
|
|
||||||
for job in render_meshes.iter() {
|
for job in render_meshes.iter() {
|
||||||
// get the mesh (containing vertices) and the buffers from storage
|
// 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() {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
let buffers = buffers.unwrap();
|
let buffers = buffers.unwrap();
|
||||||
|
|
|
@ -30,3 +30,6 @@ pub use mesh_prepare::*;
|
||||||
|
|
||||||
mod transform;
|
mod transform;
|
||||||
pub use transform::*;
|
pub use transform::*;
|
||||||
|
|
||||||
|
mod sprite;
|
||||||
|
pub use sprite::*;
|
|
@ -14,17 +14,13 @@ use lyra_ecs::{
|
||||||
};
|
};
|
||||||
use lyra_game_derive::RenderGraphLabel;
|
use lyra_game_derive::RenderGraphLabel;
|
||||||
use lyra_math::{Angle, Transform};
|
use lyra_math::{Angle, Transform};
|
||||||
|
use lyra_resource::{RequestError, ResHandle};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
use crate::render::{
|
use crate::render::{
|
||||||
graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue},
|
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, PackedTextureAtlas
|
||||||
light::{directional::DirectionalLight, LightType, PointLight, SpotLight},
|
|
||||||
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
|
|
||||||
transform_buffer_storage::TransformBuffers,
|
|
||||||
vertex::Vertex,
|
|
||||||
AtlasFrame, GpuSlotBuffer, TextureAtlas,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
|
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
|
||||||
|
@ -74,7 +70,7 @@ pub struct ShadowMapsPass {
|
||||||
transform_buffers: Option<ResourceData>,
|
transform_buffers: Option<ResourceData>,
|
||||||
render_meshes: Option<ResourceData>,
|
render_meshes: Option<ResourceData>,
|
||||||
mesh_buffers: Option<ResourceData>,
|
mesh_buffers: Option<ResourceData>,
|
||||||
shader: Option<String>,
|
shader: ResHandle<Shader>,
|
||||||
pipeline: Option<RenderPipeline>,
|
pipeline: Option<RenderPipeline>,
|
||||||
point_light_pipeline: Option<RenderPipeline>,
|
point_light_pipeline: Option<RenderPipeline>,
|
||||||
|
|
||||||
|
@ -102,7 +98,7 @@ impl ShadowMapsPass {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let atlas = TextureAtlas::new(
|
let atlas = PackedTextureAtlas::new(
|
||||||
device,
|
device,
|
||||||
wgpu::TextureFormat::Depth32Float,
|
wgpu::TextureFormat::Depth32Float,
|
||||||
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
@ -171,7 +167,7 @@ impl ShadowMapsPass {
|
||||||
transform_buffers: None,
|
transform_buffers: None,
|
||||||
render_meshes: None,
|
render_meshes: None,
|
||||||
mesh_buffers: None,
|
mesh_buffers: None,
|
||||||
shader: None,
|
shader: Default::default(),
|
||||||
pipeline: None,
|
pipeline: None,
|
||||||
point_light_pipeline: None,
|
point_light_pipeline: None,
|
||||||
|
|
||||||
|
@ -503,21 +499,14 @@ impl ShadowMapsPass {
|
||||||
queue.write_buffer(buffer, 0, bytemuck::cast_slice(points.as_slice()));
|
queue.write_buffer(buffer, 0, bytemuck::cast_slice(points.as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register all the shaders, returning the module of the
|
/// Load all shadow related registers and return the shader for the shadow depth pass.
|
||||||
fn register_shaders(&self, graph: &mut RenderGraph) -> Result<(), wgsl_preprocessor::Error> {
|
fn loader_shaders(&self, graph: &mut RenderGraph) -> Result<ResHandle<Shader>, RequestError> {
|
||||||
let src = include_str!("../../shaders/shadows/shadows_structs.wgsl");
|
graph.load_shader_str("shadows_structs", include_str!("../../shaders/shadows/shadows_structs.wgsl"))?;
|
||||||
graph.register_shader(src)?;
|
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");
|
Ok(depth)
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,11 +515,8 @@ impl Node for ShadowMapsPass {
|
||||||
&mut self,
|
&mut self,
|
||||||
graph: &mut crate::render::graph::RenderGraph,
|
graph: &mut crate::render::graph::RenderGraph,
|
||||||
) -> crate::render::graph::NodeDesc {
|
) -> crate::render::graph::NodeDesc {
|
||||||
self.register_shaders(graph)
|
self.shader = self.loader_shaders(graph)
|
||||||
.expect("failed to register shaders");
|
.expect("failed to load depth shadow shader");
|
||||||
self.shader = Some(graph.preprocess_shader("lyra::shadows::depth_pass")
|
|
||||||
.expect("failed to preprocess depth shadow shaders"));
|
|
||||||
println!("{}", self.shader.as_ref().unwrap());
|
|
||||||
|
|
||||||
let mut node = NodeDesc::new(NodeType::Render, None, vec![]);
|
let mut node = NodeDesc::new(NodeType::Render, None, vec![]);
|
||||||
|
|
||||||
|
@ -773,11 +759,6 @@ impl Node for ShadowMapsPass {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.pipeline.is_none() {
|
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 bgl = self.bgl.clone();
|
||||||
let transforms = self.transform_buffers().bindgroup_layout.clone();
|
let transforms = self.transform_buffers().bindgroup_layout.clone();
|
||||||
|
|
||||||
|
@ -788,7 +769,7 @@ impl Node for ShadowMapsPass {
|
||||||
layouts: vec![bgl.clone(), transforms.clone()],
|
layouts: vec![bgl.clone(), transforms.clone()],
|
||||||
push_constant_ranges: vec![],
|
push_constant_ranges: vec![],
|
||||||
vertex: VertexState {
|
vertex: VertexState {
|
||||||
module: shader.clone(),
|
module: self.shader.clone(),
|
||||||
entry_point: "vs_main".into(),
|
entry_point: "vs_main".into(),
|
||||||
buffers: vec![Vertex::position_desc().into()],
|
buffers: vec![Vertex::position_desc().into()],
|
||||||
},
|
},
|
||||||
|
@ -817,12 +798,12 @@ impl Node for ShadowMapsPass {
|
||||||
layouts: vec![bgl, transforms],
|
layouts: vec![bgl, transforms],
|
||||||
push_constant_ranges: vec![],
|
push_constant_ranges: vec![],
|
||||||
vertex: VertexState {
|
vertex: VertexState {
|
||||||
module: shader.clone(),
|
module: self.shader.clone(),
|
||||||
entry_point: "vs_main".into(),
|
entry_point: "vs_main".into(),
|
||||||
buffers: vec![Vertex::position_desc().into()],
|
buffers: vec![Vertex::position_desc().into()],
|
||||||
},
|
},
|
||||||
fragment: Some(FragmentState {
|
fragment: Some(FragmentState {
|
||||||
module: shader,
|
module: self.shader.clone(),
|
||||||
entry_point: "fs_point_light_main".into(),
|
entry_point: "fs_point_light_main".into(),
|
||||||
targets: vec![],
|
targets: vec![],
|
||||||
}),
|
}),
|
||||||
|
@ -963,9 +944,9 @@ fn light_shadow_pass_impl<'a>(
|
||||||
|
|
||||||
for job in render_meshes.iter() {
|
for job in render_meshes.iter() {
|
||||||
// get the mesh (containing vertices) and the buffers from storage
|
// 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() {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
let buffers = buffers.unwrap();
|
let buffers = buffers.unwrap();
|
||||||
|
@ -1081,14 +1062,14 @@ impl LightShadowMapId {
|
||||||
|
|
||||||
/// An ecs resource storing the [`TextureAtlas`] of shadow maps.
|
/// An ecs resource storing the [`TextureAtlas`] of shadow maps.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LightShadowMapAtlas(Arc<RwLock<TextureAtlas>>);
|
pub struct LightShadowMapAtlas(Arc<RwLock<PackedTextureAtlas>>);
|
||||||
|
|
||||||
impl LightShadowMapAtlas {
|
impl LightShadowMapAtlas {
|
||||||
pub fn get(&self) -> RwLockReadGuard<TextureAtlas> {
|
pub fn get(&self) -> RwLockReadGuard<PackedTextureAtlas> {
|
||||||
self.0.read().unwrap()
|
self.0.read().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&self) -> RwLockWriteGuard<TextureAtlas> {
|
pub fn get_mut(&self) -> RwLockWriteGuard<PackedTextureAtlas> {
|
||||||
self.0.write().unwrap()
|
self.0.write().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,491 @@
|
||||||
|
use std::{collections::VecDeque, sync::Arc};
|
||||||
|
|
||||||
|
use glam::{UVec2, Vec2, Vec3};
|
||||||
|
use image::GenericImageView;
|
||||||
|
use lyra_ecs::{
|
||||||
|
query::{filter::Or, Entities, ResMut, TickOf}, AtomicRef, Entity, ResourceData
|
||||||
|
};
|
||||||
|
use lyra_game_derive::RenderGraphLabel;
|
||||||
|
use lyra_math::URect;
|
||||||
|
use lyra_resource::Image;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use tracing::{debug, instrument, warn};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
render::{
|
||||||
|
graph::{Node, NodeDesc, NodeType, SlotAttribute},
|
||||||
|
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
||||||
|
transform_buffer_storage::{TransformBuffers, TransformIndex},
|
||||||
|
vertex::Vertex2D,
|
||||||
|
},
|
||||||
|
sprite::{AtlasSprite, Sprite},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{BasePassSlots, RenderAssets};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RenderJob {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub shader_id: u64,
|
||||||
|
pub asset_uuid: uuid::Uuid,
|
||||||
|
pub transform_id: TransformIndex,
|
||||||
|
pub atlas_frame_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpriteBuffers {
|
||||||
|
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>,
|
||||||
|
/// Buffer that stores a `Rect` with `min` and `max` set to zero.
|
||||||
|
/// This can be used for sprites that are not from an atlas.
|
||||||
|
atlas_frames_buf: Option<Arc<wgpu::Buffer>>,
|
||||||
|
|
||||||
|
transform_buffers: Option<ResourceData>,
|
||||||
|
texture_store: Option<ResourceData>,
|
||||||
|
buffer_store: Option<ResourceData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpritePass {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self, device))]
|
||||||
|
fn create_vertex_index_buffers(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
dimensions: UVec2,
|
||||||
|
) -> (wgpu::Buffer, wgpu::Buffer) {
|
||||||
|
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, dimensions.y as f32, 0.0), Vec2::new(0.0, 0.0)),
|
||||||
|
// top right
|
||||||
|
Vertex2D::new(Vec3::new(dimensions.x as f32, 0.0, 0.0), Vec2::new(1.0, 1.0)),
|
||||||
|
// bottom right
|
||||||
|
Vertex2D::new(
|
||||||
|
Vec3::new(dimensions.x as f32, dimensions.y 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 frames = self.atlas_frames_buf.as_ref().unwrap();
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
|
buffer: &frames,
|
||||||
|
offset: 0,
|
||||||
|
size: None,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
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 atlas_frames = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("default_sprite_atlas_frame"),
|
||||||
|
size: std::mem::size_of::<URect>() as u64 * 1000,
|
||||||
|
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
// write the rect for sprites that aren't part of the texture atlas.
|
||||||
|
graph
|
||||||
|
.queue
|
||||||
|
.write_buffer(&atlas_frames, 0, bytemuck::bytes_of(&URect::ZERO));
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
self.texture_bgl = Some(Arc::new(bgl));
|
||||||
|
self.atlas_frames_buf = Some(Arc::new(atlas_frames));
|
||||||
|
|
||||||
|
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 texture_store = world
|
||||||
|
.get_resource_data::<RenderAssets<SpriteTexture>>().unwrap();
|
||||||
|
self.texture_store = Some(texture_store.clone());
|
||||||
|
|
||||||
|
world.add_resource_default_if_absent::<FxHashMap<Entity, SpriteBuffers>>();
|
||||||
|
let buffer_store = world
|
||||||
|
.get_resource_data::<FxHashMap<Entity, SpriteBuffers>>().unwrap();
|
||||||
|
self.buffer_store = Some(buffer_store.clone());
|
||||||
|
|
||||||
|
let transforms = world
|
||||||
|
.get_resource_data::<TransformBuffers>()
|
||||||
|
.expect("Missing transform buffers");
|
||||||
|
self.transform_buffers = Some(transforms.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut v = Vec::with_capacity(500);
|
||||||
|
|
||||||
|
let world_tick = world.current_tick();
|
||||||
|
let queue = &graph.queue;
|
||||||
|
for (entity, (sprite, atlas_sprite), transform_idx, mut texture_store, mut buffer_store) in world
|
||||||
|
.view::<(
|
||||||
|
Entities,
|
||||||
|
Or<&Sprite, (&AtlasSprite, TickOf<AtlasSprite>)>,
|
||||||
|
&TransformIndex,
|
||||||
|
ResMut<RenderAssets<SpriteTexture>>,
|
||||||
|
ResMut<FxHashMap<Entity, SpriteBuffers>>,
|
||||||
|
)>()
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
let tex = if let Some(sprite) = &sprite {
|
||||||
|
sprite.texture.clone()//.data_ref()
|
||||||
|
} else if let Some((a, _)) = &atlas_sprite {
|
||||||
|
a.atlas.data_ref()
|
||||||
|
.unwrap().texture.clone()
|
||||||
|
} else { continue; };
|
||||||
|
let rect = atlas_sprite.as_ref().map(|(a, _)| a.sprite);
|
||||||
|
|
||||||
|
if let Some(image) = tex.data_ref() {
|
||||||
|
let texture_uuid = tex.uuid();
|
||||||
|
if !texture_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)
|
||||||
|
{
|
||||||
|
texture_store.insert(
|
||||||
|
texture_uuid,
|
||||||
|
SpriteTexture {
|
||||||
|
texture,
|
||||||
|
sampler,
|
||||||
|
texture_bg: Arc::new(tex_bg),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !buffer_store.contains_key(&entity) {
|
||||||
|
let dim = rect.unwrap_or_else(|| {
|
||||||
|
let i = image.dimensions();
|
||||||
|
URect::new(0, 0, i.0, i.1)
|
||||||
|
});
|
||||||
|
debug!("storing rect: {dim:?}");
|
||||||
|
let dim = dim.dimensions();
|
||||||
|
let (vertex, index) = self.create_vertex_index_buffers(device, dim);
|
||||||
|
buffer_store.insert(entity, SpriteBuffers { vertex_buffers: vertex, index_buffers: index });
|
||||||
|
} else if let Some((ats, tick)) = &atlas_sprite {
|
||||||
|
// detect a change for the vertex and index buffers of the sprite
|
||||||
|
if tick.checked_sub(1).unwrap_or(0) >= *world_tick {
|
||||||
|
debug!("Updating buffer for entity after change detected in atlas sprite");
|
||||||
|
let dim = ats.sprite.dimensions();
|
||||||
|
let (vertex, index) = self.create_vertex_index_buffers(device, dim);
|
||||||
|
buffer_store.insert(entity, SpriteBuffers { vertex_buffers: vertex, index_buffers: index });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame_id = match rect {
|
||||||
|
Some(r) => {
|
||||||
|
v.push(r);
|
||||||
|
// No -1 here since the gpu buffer is already offset by 1
|
||||||
|
// to store the default rect at the front
|
||||||
|
v.len() as u64
|
||||||
|
}
|
||||||
|
None => 0
|
||||||
|
};
|
||||||
|
|
||||||
|
self.jobs.push_back(RenderJob {
|
||||||
|
entity,
|
||||||
|
shader_id: 0,
|
||||||
|
asset_uuid: texture_uuid,
|
||||||
|
transform_id: *transform_idx,
|
||||||
|
atlas_frame_id: frame_id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let buf = self.atlas_frames_buf.as_ref().unwrap();
|
||||||
|
// skip default rect
|
||||||
|
queue.write_buffer(buf, std::mem::size_of::<URect>() as _, bytemuck::cast_slice(&v));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 texture_store = self.texture_store.clone().unwrap();
|
||||||
|
let texture_store: AtomicRef<RenderAssets<SpriteTexture>> = texture_store.get();
|
||||||
|
let buffer_store = self.buffer_store.clone().unwrap();
|
||||||
|
let buffer_store: AtomicRef<FxHashMap<Entity, SpriteBuffers>> = buffer_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() {
|
||||||
|
// bind texture
|
||||||
|
let tex = texture_store
|
||||||
|
.get(&job.asset_uuid)
|
||||||
|
.expect("failed to find SpriteTexture for job asset_uuid");
|
||||||
|
pass.set_bind_group(0, &tex.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, &[]);
|
||||||
|
|
||||||
|
// set vertex and index buffers
|
||||||
|
let bufs = buffer_store
|
||||||
|
.get(&job.entity)
|
||||||
|
.expect("failed to find buffers for job entity");
|
||||||
|
pass.set_vertex_buffer(0, bufs.vertex_buffers.slice(..));
|
||||||
|
pass.set_index_buffer(bufs.index_buffers.slice(..), wgpu::IndexFormat::Uint32);
|
||||||
|
|
||||||
|
// use the atlas frame id as the instance
|
||||||
|
let inst = job.atlas_frame_id as u32;
|
||||||
|
pass.draw_indexed(0..6, 0, inst..inst + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{collections::HashMap, rc::Rc, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use lyra_game_derive::RenderGraphLabel;
|
use lyra_game_derive::RenderGraphLabel;
|
||||||
|
|
||||||
use crate::render::{
|
use crate::render::{
|
||||||
graph::{Node, NodeDesc, NodeType},
|
graph::{Node, NodeDesc, NodeType},
|
||||||
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
|
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, VertexState},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||||
|
@ -58,13 +58,9 @@ impl Node for TintPass {
|
||||||
self.bgl = Some(bgl.clone());
|
self.bgl = Some(bgl.clone());
|
||||||
self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default()));
|
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 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(
|
NodeDesc::new(
|
||||||
NodeType::Render,
|
NodeType::Render,
|
||||||
|
|
|
@ -32,12 +32,12 @@ pub struct InterpTransform {
|
||||||
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||||
pub struct TransformsNodeLabel;
|
pub struct TransformsNodeLabel;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TransformsNode {}
|
pub struct TransformsNode;
|
||||||
|
|
||||||
impl TransformsNode {
|
impl TransformsNode {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {}
|
Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ pub mod render_buffer;
|
||||||
pub mod render_job;
|
pub mod render_job;
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
pub mod shader_loader;
|
|
||||||
pub mod material;
|
pub mod material;
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod transform_buffer_storage;
|
pub mod transform_buffer_storage;
|
||||||
|
@ -20,3 +19,6 @@ pub use texture_atlas::*;
|
||||||
|
|
||||||
mod slot_buffer;
|
mod slot_buffer;
|
||||||
pub use slot_buffer::*;
|
pub use slot_buffer::*;
|
||||||
|
|
||||||
|
mod shader_loader;
|
||||||
|
pub use shader_loader::*;
|
|
@ -5,7 +5,7 @@ use super::transform_buffer_storage::TransformIndex;
|
||||||
pub struct RenderJob {
|
pub struct RenderJob {
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub shader_id: u64,
|
pub shader_id: u64,
|
||||||
pub mesh_uuid: uuid::Uuid,
|
pub asset_uuid: uuid::Uuid,
|
||||||
pub transform_id: TransformIndex,
|
pub transform_id: TransformIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ impl RenderJob {
|
||||||
Self {
|
Self {
|
||||||
entity,
|
entity,
|
||||||
shader_id,
|
shader_id,
|
||||||
mesh_uuid: mesh_buffer_id,
|
asset_uuid: mesh_buffer_id,
|
||||||
transform_id
|
transform_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use lyra_game_derive::RenderGraphLabel;
|
||||||
use tracing::{debug, instrument, warn};
|
use tracing::{debug, instrument, warn};
|
||||||
use winit::window::Window;
|
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::graph::RenderGraph;
|
||||||
use super::{resource::RenderPipeline, render_job::RenderJob};
|
use super::{resource::RenderPipeline, render_job::RenderJob};
|
||||||
|
@ -135,13 +135,13 @@ impl BasicRenderer {
|
||||||
let surface_target = RenderTarget::from_surface(surface, config);
|
let surface_target = RenderTarget::from_surface(surface, config);
|
||||||
let view_target = Rc::new(RefCell::new(ViewTarget::new(device.clone(), surface_target)));
|
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");
|
debug!("Adding base pass");
|
||||||
main_graph.add_node(BasePassLabel, BasePass::new());
|
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");
|
debug!("Adding light base pass");
|
||||||
forward_plus_graph.add_node(LightBasePassLabel, LightBasePass::new());
|
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_node(MeshesPassLabel, MeshPass::new(material_bgl));
|
||||||
forward_plus_graph.add_edge(TransformsNodeLabel, MeshPrepNodeLabel);
|
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(LightBasePassLabel, LightCullComputePassLabel);
|
||||||
forward_plus_graph.add_edge(LightCullComputePassLabel, MeshesPassLabel);
|
forward_plus_graph.add_edge(LightCullComputePassLabel, MeshesPassLabel);
|
||||||
forward_plus_graph.add_edge(MeshPrepNodeLabel, MeshesPassLabel);
|
forward_plus_graph.add_edge(MeshPrepNodeLabel, MeshesPassLabel);
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
use std::{ops::Deref, rc::Rc, sync::Arc};
|
use std::{ops::Deref, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
|
use lyra_resource::ResHandle;
|
||||||
use wgpu::PipelineLayout;
|
use wgpu::PipelineLayout;
|
||||||
|
|
||||||
use super::{PipelineCompilationOptions, Shader};
|
use crate::render::Shader;
|
||||||
|
|
||||||
|
use super::PipelineCompilationOptions;
|
||||||
|
|
||||||
//#[derive(Debug, Clone)]
|
//#[derive(Debug, Clone)]
|
||||||
pub struct ComputePipelineDescriptor {
|
pub struct ComputePipelineDescriptor {
|
||||||
pub label: Option<String>,
|
pub label: Option<String>,
|
||||||
pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
|
pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
|
||||||
// TODO: make this a ResHandle<Shader>
|
|
||||||
/// The compiled shader module for the stage.
|
/// The compiled shader module for the stage.
|
||||||
pub shader: Rc<Shader>,
|
pub shader: ResHandle<Shader>,
|
||||||
/// The entry point in the compiled shader.
|
/// The entry point in the compiled shader.
|
||||||
/// There must be a function in the shader with the same name.
|
/// There must be a function in the shader with the same name.
|
||||||
pub shader_entry_point: String,
|
pub shader_entry_point: String,
|
||||||
|
@ -76,13 +78,16 @@ impl ComputePipeline {
|
||||||
None
|
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
|
// 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
|
// they share the same shader. I tried to do it without an Rc but couldn't get past
|
||||||
// the borrow checker
|
// the borrow checker
|
||||||
let compiled_shader = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
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(
|
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||||
&desc.shader.source,
|
&shader.source,
|
||||||
)),
|
)),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -84,13 +84,16 @@ impl RenderPipeline {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.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
|
// 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
|
// they share the same shader. I tried to do it without an Rc but couldn't get past
|
||||||
// the borrow checker
|
// the borrow checker
|
||||||
let vrtx_shad = Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
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(
|
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||||
&desc.vertex.module.source,
|
&vertex_shader.source,
|
||||||
)),
|
)),
|
||||||
}));
|
}));
|
||||||
let vrtx_state = wgpu::VertexState {
|
let vrtx_state = wgpu::VertexState {
|
||||||
|
@ -101,12 +104,14 @@ impl RenderPipeline {
|
||||||
};
|
};
|
||||||
|
|
||||||
let frag_module = desc.fragment.as_ref().map(|f| {
|
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()
|
vrtx_shad.clone()
|
||||||
} else {
|
} 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 {
|
Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: f.module.label.as_deref(),
|
label: frag_shader.label.as_deref(),
|
||||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)),
|
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&frag_shader.source)),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::rc::Rc;
|
use lyra_resource::ResHandle;
|
||||||
|
|
||||||
|
use crate::render::Shader;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct VertexBufferLayout {
|
pub struct VertexBufferLayout {
|
||||||
|
@ -18,11 +20,10 @@ impl<'a> From<wgpu::VertexBufferLayout<'a>> for VertexBufferLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes the vertex stage in a render pipeline.
|
/// Describes the vertex stage in a render pipeline.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VertexState {
|
pub struct VertexState {
|
||||||
// TODO: make this a ResHandle<Shader>
|
|
||||||
/// The compiled shader module for the stage.
|
/// The compiled shader module for the stage.
|
||||||
pub module: Rc<Shader>,
|
pub module: ResHandle<Shader>,
|
||||||
/// The entry point in the compiled shader.
|
/// The entry point in the compiled shader.
|
||||||
/// There must be a function in the shader with the same name.
|
/// There must be a function in the shader with the same name.
|
||||||
pub entry_point: String,
|
pub entry_point: String,
|
||||||
|
@ -30,18 +31,11 @@ pub struct VertexState {
|
||||||
pub buffers: Vec<VertexBufferLayout>,
|
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.
|
/// Describes the fragment stage in the render pipeline.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FragmentState {
|
pub struct FragmentState {
|
||||||
// TODO: make this a ResHandle<Shader>
|
|
||||||
/// The compiled shader module for the stage.
|
/// The compiled shader module for the stage.
|
||||||
pub module: Rc<Shader>,
|
pub module: ResHandle<Shader>,
|
||||||
/// The entry point in the compiled shader.
|
/// The entry point in the compiled shader.
|
||||||
/// There must be a function in the shader with the same name.
|
/// There must be a function in the shader with the same name.
|
||||||
pub entry_point: String,
|
pub entry_point: String,
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
const ALPHA_CUTOFF = 0.1;
|
||||||
|
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) position: vec3<f32>,
|
||||||
|
@location(1) tex_coords: vec2<f32>,
|
||||||
|
@builtin(instance_index) instance_index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
@location(0) tex_coords: vec2<f32>,
|
||||||
|
@location(1) world_position: vec3<f32>,
|
||||||
|
@location(2) instance_index: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransformData {
|
||||||
|
transform: mat4x4<f32>,
|
||||||
|
normal_matrix: mat4x4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct URect {
|
||||||
|
min: vec2<u32>,
|
||||||
|
max: vec2<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
out.instance_index = in.instance_index;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var t_diffuse: texture_2d<f32>;
|
||||||
|
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var s_diffuse: sampler;
|
||||||
|
|
||||||
|
@group(0) @binding(2)
|
||||||
|
var<storage, read> u_sprite_atlas_frames: array<URect>;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let frame = u_sprite_atlas_frames[in.instance_index];
|
||||||
|
var region_coords = in.tex_coords;
|
||||||
|
|
||||||
|
if (frame.min.x != 0 || frame.min.y != 0 || frame.max.x != 0 || frame.max.y != 0) {
|
||||||
|
let dim = vec2<f32>(textureDimensions(t_diffuse));
|
||||||
|
// convert tex coords to frame
|
||||||
|
region_coords = vec2<f32>(
|
||||||
|
mix(f32(frame.min.x), f32(frame.max.x), in.tex_coords.x),
|
||||||
|
mix(f32(frame.min.y), f32(frame.max.y), in.tex_coords.y)
|
||||||
|
);
|
||||||
|
// convert frame coords to texture coords
|
||||||
|
region_coords /= dim;
|
||||||
|
}
|
||||||
|
|
||||||
|
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, region_coords);
|
||||||
|
if (object_color.a < ALPHA_CUTOFF) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
return object_color;
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ struct CameraUniform {
|
||||||
projection: mat4x4<f32>,
|
projection: mat4x4<f32>,
|
||||||
position: vec3f,
|
position: vec3f,
|
||||||
tile_debug: u32,
|
tile_debug: u32,
|
||||||
};
|
}
|
||||||
|
|
||||||
struct Light {
|
struct Light {
|
||||||
position: vec3f,
|
position: vec3f,
|
||||||
|
@ -32,12 +32,12 @@ struct Light {
|
||||||
spot_cutoff: f32,
|
spot_cutoff: f32,
|
||||||
spot_outer_cutoff: f32,
|
spot_outer_cutoff: f32,
|
||||||
light_shadow_uniform_index: array<i32, 6>,
|
light_shadow_uniform_index: array<i32, 6>,
|
||||||
};
|
}
|
||||||
|
|
||||||
struct Lights {
|
struct Lights {
|
||||||
light_count: u32,
|
light_count: u32,
|
||||||
data: array<Light>,
|
data: array<Light>,
|
||||||
};
|
}
|
||||||
|
|
||||||
struct Cone {
|
struct Cone {
|
||||||
tip: vec3f,
|
tip: vec3f,
|
||||||
|
|
|
@ -28,7 +28,7 @@ impl AtlasFrame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextureAtlas<P: AtlasPacker = SkylinePacker> {
|
pub struct PackedTextureAtlas<P: AtlasPacker = SkylinePacker> {
|
||||||
atlas_size: UVec2,
|
atlas_size: UVec2,
|
||||||
|
|
||||||
texture_format: wgpu::TextureFormat,
|
texture_format: wgpu::TextureFormat,
|
||||||
|
@ -38,7 +38,7 @@ pub struct TextureAtlas<P: AtlasPacker = SkylinePacker> {
|
||||||
packer: P,
|
packer: P,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: AtlasPacker> TextureAtlas<P> {
|
impl<P: AtlasPacker> PackedTextureAtlas<P> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
|
|
|
@ -75,7 +75,7 @@ impl Vertex2D {
|
||||||
|
|
||||||
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||||
wgpu::VertexBufferLayout {
|
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,
|
step_mode: wgpu::VertexStepMode::Vertex,
|
||||||
attributes: &[
|
attributes: &[
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
|
|
|
@ -1,36 +1,206 @@
|
||||||
use lyra_ecs::Component;
|
use glam::{Mat4, Vec2};
|
||||||
|
use lyra_ecs::{Bundle, Component};
|
||||||
|
use lyra_math::{Angle, Rect};
|
||||||
use lyra_reflect::Reflect;
|
use lyra_reflect::Reflect;
|
||||||
|
|
||||||
use crate::{math::{Angle, Transform}, render::camera::CameraProjectionMode};
|
/// The method of scaling for an [`OrthographicProjection`].
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
|
||||||
#[derive(Clone, Component, Reflect)]
|
pub enum ScaleMode {
|
||||||
pub struct CameraComponent {
|
/// No scaling, keep everything the same size as the viewport.
|
||||||
pub transform: Transform,
|
Viewport,
|
||||||
pub fov: Angle,
|
/// The width of the projection in world units.
|
||||||
pub mode: CameraProjectionMode,
|
///
|
||||||
pub debug: bool,
|
/// Height will be set based off of the viewport's aspect ratio.
|
||||||
|
Width(f32),
|
||||||
|
/// The height of the projection in world units.
|
||||||
|
///
|
||||||
|
/// Width will be set based off of the viewport's aspect ratio.
|
||||||
|
Height(f32),
|
||||||
|
/// The exact size of the projection in world units.
|
||||||
|
///
|
||||||
|
/// Ignoring viewport size, likely causes stretching.
|
||||||
|
Size(Vec2),
|
||||||
|
/// Keep the projection size below a maximum value in world units, while keeping the aspect ratio.
|
||||||
|
MaxSize(Vec2),
|
||||||
|
/// Keep the projection size above a minimum value in world units, while keeping the aspect ratio.
|
||||||
|
MinSize(Vec2),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CameraComponent {
|
impl Default for ScaleMode {
|
||||||
|
fn default() -> Self {
|
||||||
|
ScaleMode::Viewport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
|
||||||
|
pub struct OrthographicProjection {
|
||||||
|
pub scale_mode: ScaleMode,
|
||||||
|
pub scale: f32,
|
||||||
|
pub znear: f32,
|
||||||
|
pub zfar: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OrthographicProjection {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
scale_mode: Default::default(),
|
||||||
|
scale: 1.0,
|
||||||
|
znear: 0.0,
|
||||||
|
zfar: 1000.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OrthographicProjection {
|
||||||
|
fn get_rect(&self, viewport_size: Vec2) -> Rect {
|
||||||
|
let origin;
|
||||||
|
let size;
|
||||||
|
|
||||||
|
match self.scale_mode {
|
||||||
|
ScaleMode::Viewport => {
|
||||||
|
origin = viewport_size * 0.5;
|
||||||
|
size = viewport_size;
|
||||||
|
},
|
||||||
|
ScaleMode::Width(width) => {
|
||||||
|
let aspect = viewport_size.x / viewport_size.y;
|
||||||
|
let origin_x = width * 0.5;
|
||||||
|
let scaled_height = width / aspect;
|
||||||
|
let origin_y = scaled_height * 0.5;
|
||||||
|
|
||||||
|
origin = Vec2::new(origin_x, origin_y);
|
||||||
|
size = Vec2::new(width, scaled_height);
|
||||||
|
},
|
||||||
|
ScaleMode::Height(height) => {
|
||||||
|
let aspect = viewport_size.x / viewport_size.y;
|
||||||
|
let origin_y = height * 0.5;
|
||||||
|
let scaled_width = height * aspect;
|
||||||
|
let origin_x = scaled_width * 0.5;
|
||||||
|
|
||||||
|
origin = Vec2::new(origin_x, origin_y);
|
||||||
|
size = Vec2::new(scaled_width, height);
|
||||||
|
},
|
||||||
|
ScaleMode::Size(s) => {
|
||||||
|
origin = s * 0.5;
|
||||||
|
size = s;
|
||||||
|
},
|
||||||
|
ScaleMode::MaxSize(s) => {
|
||||||
|
let clamped = s.min(viewport_size);
|
||||||
|
origin = clamped * 0.5;
|
||||||
|
size = clamped;
|
||||||
|
},
|
||||||
|
ScaleMode::MinSize(s) => {
|
||||||
|
let clamped = s.max(viewport_size);
|
||||||
|
origin = clamped * 0.5;
|
||||||
|
size = clamped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect::new(self.scale * -origin.x,
|
||||||
|
self.scale * -origin.y,
|
||||||
|
self.scale * size.x - origin.x,
|
||||||
|
self.scale * size.y - origin.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_mat(&self, viewport_size: Vec2) -> Mat4 {
|
||||||
|
let rect = self.get_rect(viewport_size);
|
||||||
|
|
||||||
|
glam::Mat4::orthographic_rh(
|
||||||
|
rect.min.x,
|
||||||
|
rect.max.x,
|
||||||
|
rect.min.y,
|
||||||
|
rect.max.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 {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
transform: Transform::default(),
|
|
||||||
fov: Angle::Degrees(45.0),
|
fov: Angle::Degrees(45.0),
|
||||||
mode: CameraProjectionMode::Perspective,
|
znear: 0.1,
|
||||||
debug: false,
|
zfar: 100.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraComponent {
|
impl PerspectiveProjection {
|
||||||
pub fn new_3d() -> Self {
|
pub fn to_mat(&self, viewport_size: Vec2) -> Mat4 {
|
||||||
Self::default()
|
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 {
|
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Component)]
|
||||||
CameraComponent {
|
pub enum CameraProjection {
|
||||||
mode: CameraProjectionMode::Orthographic,
|
/// 3d camera projection
|
||||||
..Default::default()
|
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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
use glam::{EulerRot, Quat, Vec3};
|
use glam::{EulerRot, Quat, Vec3};
|
||||||
use lyra_ecs::{query::{Res, View}, Component};
|
use lyra_ecs::{query::{Res, View}, Component};
|
||||||
|
use lyra_math::Transform;
|
||||||
use lyra_reflect::Reflect;
|
use lyra_reflect::Reflect;
|
||||||
|
|
||||||
use crate::{game::App, input::ActionHandler, plugin::Plugin, DeltaTime};
|
use crate::{game::App, input::ActionHandler, plugin::Plugin, DeltaTime};
|
||||||
|
|
||||||
use super::CameraComponent;
|
|
||||||
|
|
||||||
pub const ACTLBL_MOVE_UP_DOWN: &str = "MoveUpDown";
|
pub const ACTLBL_MOVE_UP_DOWN: &str = "MoveUpDown";
|
||||||
pub const ACTLBL_MOVE_LEFT_RIGHT: &str = "MoveLeftRight";
|
pub const ACTLBL_MOVE_LEFT_RIGHT: &str = "MoveLeftRight";
|
||||||
pub const ACTLBL_MOVE_FORWARD_BACKWARD: &str = "MoveForwardBackward";
|
pub const ACTLBL_MOVE_FORWARD_BACKWARD: &str = "MoveForwardBackward";
|
||||||
|
@ -14,13 +13,13 @@ pub const ACTLBL_LOOK_UP_DOWN: &str = "LookUpDown";
|
||||||
pub const ACTLBL_LOOK_ROLL: &str = "LookRoll";
|
pub const ACTLBL_LOOK_ROLL: &str = "LookRoll";
|
||||||
|
|
||||||
#[derive(Clone, Component, Reflect)]
|
#[derive(Clone, Component, Reflect)]
|
||||||
pub struct FreeFlyCamera {
|
pub struct FreeFly3dCamera {
|
||||||
pub speed: f32,
|
pub speed: f32,
|
||||||
pub look_speed: f32,
|
pub look_speed: f32,
|
||||||
pub mouse_sensitivity: f32,
|
pub mouse_sensitivity: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FreeFlyCamera {
|
impl Default for FreeFly3dCamera {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
speed: 4.0,
|
speed: 4.0,
|
||||||
|
@ -30,7 +29,7 @@ impl Default for FreeFlyCamera {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FreeFlyCamera {
|
impl FreeFly3dCamera {
|
||||||
pub fn new(speed: f32, look_speed: f32, mouse_sensitivity: f32) -> Self {
|
pub fn new(speed: f32, look_speed: f32, mouse_sensitivity: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
speed,
|
speed,
|
||||||
|
@ -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_3d_camera_controller(delta_time: Res<DeltaTime>, handler: Res<ActionHandler>, view: View<(&mut Transform, &FreeFly3dCamera)>) -> anyhow::Result<()> {
|
||||||
let delta_time = **delta_time;
|
let delta_time = **delta_time;
|
||||||
for (mut cam, fly) in view.into_iter() {
|
for (mut transform, fly) in view.into_iter() {
|
||||||
let forward = cam.transform.forward();
|
let forward = transform.forward();
|
||||||
let left = cam.transform.left();
|
let left = transform.left();
|
||||||
let up = Vec3::Y;
|
let up = Vec3::Y;
|
||||||
|
|
||||||
let move_y = handler.get_axis_modifier(ACTLBL_MOVE_UP_DOWN).unwrap_or(0.0);
|
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;
|
velocity += move_z * forward;
|
||||||
|
|
||||||
if velocity != Vec3::ZERO {
|
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);
|
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 {
|
if camera_rot != Vec3::ZERO {
|
||||||
let look_velocity = camera_rot * fly.look_speed * delta_time;
|
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;
|
x += look_velocity.x;
|
||||||
y += look_velocity.y;
|
y += look_velocity.y;
|
||||||
x = x.clamp(-1.54, 1.54);
|
x = x.clamp(-1.54, 1.54);
|
||||||
|
|
||||||
// rotation is not commutative, keep this order to avoid unintended roll
|
// 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);
|
* Quat::from_axis_angle(Vec3::X, x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +91,6 @@ pub struct FreeFlyCameraPlugin;
|
||||||
|
|
||||||
impl Plugin for FreeFlyCameraPlugin {
|
impl Plugin for FreeFlyCameraPlugin {
|
||||||
fn setup(&mut self, app: &mut App) {
|
fn setup(&mut self, app: &mut App) {
|
||||||
app.with_system("free_fly_camera_system", free_fly_camera_controller, &[]);
|
app.with_system("free_fly_camera_system", free_fly_3d_camera_controller, &[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
pub mod camera;
|
mod camera;
|
||||||
pub use camera::*;
|
pub use camera::*;
|
||||||
|
|
||||||
pub mod free_fly_camera;
|
mod free_fly_camera;
|
||||||
pub use free_fly_camera::*;
|
pub use free_fly_camera::*;
|
||||||
|
|
||||||
|
mod top_down_controller;
|
||||||
|
pub use top_down_controller::*;
|
||||||
|
|
||||||
pub use lyra_scene::*;
|
pub use lyra_scene::*;
|
|
@ -0,0 +1,100 @@
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
|
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::{CameraProjection, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN};
|
||||||
|
|
||||||
|
#[derive(Clone, Component, Reflect)]
|
||||||
|
pub struct TopDown2dCamera {
|
||||||
|
pub speed: f32,
|
||||||
|
/// The zoom speed of the camera, set to `None` to disable zooming.
|
||||||
|
pub zoom_speed: Option<f32>,
|
||||||
|
/// The smallest scale of the orthographic projection view.
|
||||||
|
///
|
||||||
|
/// When the scale of the projection approaches zero, the more zoomed in the camera
|
||||||
|
/// will appear.
|
||||||
|
pub max_zoom: f32,
|
||||||
|
pub min_zoom: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TopDown2dCamera {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
speed: 5.0,
|
||||||
|
zoom_speed: None,
|
||||||
|
max_zoom: 0.36,
|
||||||
|
min_zoom: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn top_down_2d_camera_controller(delta_time: Res<DeltaTime>, handler: Res<ActionHandler>, view: View<(&mut Transform, &mut CameraProjection, &TopDown2dCamera)>) -> anyhow::Result<()> {
|
||||||
|
let delta_time = **delta_time;
|
||||||
|
for (mut transform, mut proj, controller) in view.into_iter() {
|
||||||
|
let left = transform.left();
|
||||||
|
let up = Vec3::Y;
|
||||||
|
|
||||||
|
let move_y = handler.get_axis_modifier(ACTLBL_MOVE_FORWARD_BACKWARD).unwrap_or(0.0);
|
||||||
|
let move_x = handler.get_axis_modifier(ACTLBL_MOVE_LEFT_RIGHT).unwrap_or(0.0);
|
||||||
|
let move_z = handler.get_axis_modifier(ACTLBL_MOVE_UP_DOWN);
|
||||||
|
|
||||||
|
let mut velocity = Vec3::ZERO;
|
||||||
|
velocity += move_y * up;
|
||||||
|
velocity += move_x * left;
|
||||||
|
|
||||||
|
if let (Some(zoom_speed), Some(move_z)) = (controller.zoom_speed, move_z) {
|
||||||
|
match proj.deref_mut() {
|
||||||
|
CameraProjection::Orthographic(ortho) => {
|
||||||
|
let m = move_z * zoom_speed * delta_time;
|
||||||
|
ortho.scale = (ortho.scale + m).clamp(controller.max_zoom, controller.min_zoom);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if velocity != Vec3::ZERO {
|
||||||
|
transform.translation += velocity.normalize() * controller.speed * delta_time; // TODO: speeding up
|
||||||
|
}
|
||||||
|
|
||||||
|
let motion_x = handler.get_axis_modifier(ACTLBL_LOOK_LEFT_RIGHT).unwrap_or(0.0);
|
||||||
|
let motion_y = handler.get_axis_modifier(ACTLBL_LOOK_UP_DOWN).unwrap_or(0.0);
|
||||||
|
let motion_z = handler.get_axis_modifier(ACTLBL_LOOK_ROLL).unwrap_or(0.0);
|
||||||
|
|
||||||
|
let mut camera_rot = Vec3::ZERO;
|
||||||
|
camera_rot.y -= motion_x;
|
||||||
|
camera_rot.x -= motion_y;
|
||||||
|
camera_rot.z -= motion_z;
|
||||||
|
|
||||||
|
if camera_rot != Vec3::ZERO {
|
||||||
|
let look_velocity = camera_rot * delta_time;
|
||||||
|
|
||||||
|
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
|
||||||
|
transform.rotation = Quat::from_axis_angle(Vec3::Y, y)
|
||||||
|
* Quat::from_axis_angle(Vec3::X, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A plugin that adds the top down 2d camera controller system to the world.
|
||||||
|
///
|
||||||
|
/// It is expected that there is a [`TopDown2dCamera`] in the world, if there isn't,
|
||||||
|
/// the camera would not move.
|
||||||
|
pub struct TopDown2dCameraPlugin;
|
||||||
|
|
||||||
|
impl Plugin for TopDown2dCameraPlugin {
|
||||||
|
fn setup(&mut self, app: &mut App) {
|
||||||
|
app.with_system("top_down_2d_camera_system", top_down_2d_camera_controller, &[]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,298 @@
|
||||||
|
use std::cell::RefMut;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use lyra_ecs::query::filter::Or;
|
||||||
|
use lyra_ecs::query::{Entities, Res, View};
|
||||||
|
use lyra_ecs::{Commands, Component, Entity};
|
||||||
|
use lyra_math::URect;
|
||||||
|
use lyra_reflect::Reflect;
|
||||||
|
use lyra_resource::{ResHandle, ResourceStorage};
|
||||||
|
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::DeltaTime;
|
||||||
|
|
||||||
|
use super::{AtlasSprite, TextureAtlas};
|
||||||
|
|
||||||
|
/// A struct describing an animation of a Sprite.
|
||||||
|
///
|
||||||
|
/// This is a single animation for a [`TextureAtlas`]. This is used alongside [`AtlasAnimations`]
|
||||||
|
/// to use animations from an atlas.
|
||||||
|
#[derive(Clone, Component, Reflect)]
|
||||||
|
pub struct SpriteAnimation {
|
||||||
|
/// The name of the animation.
|
||||||
|
pub name: String,
|
||||||
|
/// The frames of the animation.
|
||||||
|
pub frames: Vec<URect>,
|
||||||
|
/// The length of time a frame is displayed.
|
||||||
|
pub frame_time: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpriteAnimation {
|
||||||
|
/// Create an animation from a texture atlas.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `name` - The name of the animation. Used to identify the animation in [`AtlasAnimations`].
|
||||||
|
/// * `frame_time` - The time per frame of the animation.
|
||||||
|
/// * `atlas` - The texture atlas that this animation is from, used to acquire `self.frames`.
|
||||||
|
/// * `sprites` are the rect indexes in the atlas for this animation.
|
||||||
|
pub fn from_atlas<I>(name: &str, frame_time: f32, atlas: &TextureAtlas, sprites: I) -> Self
|
||||||
|
where
|
||||||
|
I: Iterator<Item = u32>,
|
||||||
|
{
|
||||||
|
let mut frames = vec![]; //Vec::with_capacity(sprites.len());
|
||||||
|
|
||||||
|
for i in sprites {
|
||||||
|
let r = atlas.frames.get(i as usize).cloned().unwrap();
|
||||||
|
frames.push(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
frames,
|
||||||
|
frame_time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper trait that makes it easier to create the animations for an [`AtlasAnimations`] component.
|
||||||
|
///
|
||||||
|
/// See [`AtlasAnimations::new`].
|
||||||
|
pub trait IntoSpriteAnimation {
|
||||||
|
fn into_animation(&self, atlas: &TextureAtlas) -> SpriteAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoSpriteAnimation for SpriteAnimation {
|
||||||
|
fn into_animation(&self, _: &TextureAtlas) -> SpriteAnimation {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: Iterator<Item = u32> + Clone> IntoSpriteAnimation for (&'a str, f32, I) {
|
||||||
|
fn into_animation(&self, atlas: &TextureAtlas) -> SpriteAnimation {
|
||||||
|
SpriteAnimation::from_atlas(self.0, self.1, atlas, self.2.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Component, Reflect)]
|
||||||
|
pub struct AtlasAnimations {
|
||||||
|
/// The texture atlas to get the animations from.
|
||||||
|
pub atlas: ResHandle<TextureAtlas>,
|
||||||
|
/// Animations in the atlas.
|
||||||
|
pub animations: HashMap<String, SpriteAnimation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtlasAnimations {
|
||||||
|
pub fn from_animations(
|
||||||
|
atlas: ResHandle<TextureAtlas>,
|
||||||
|
animations: Vec<SpriteAnimation>,
|
||||||
|
) -> Self {
|
||||||
|
let animations = animations
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| (a.name.clone(), a))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
Self { atlas, animations }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper for creating [`AtlasAnimations`].
|
||||||
|
///
|
||||||
|
/// If you already have the [`SpriteAnimation`]s, you can just use
|
||||||
|
/// [`AtlasAnimations::from_animations`] instead of this helper function.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// let animations = AtlasAnimations::new(atlas, &[
|
||||||
|
/// // This slice accepts anything that implements `IntoSpriteAnimation`:
|
||||||
|
/// // * tuple of (name: &str, frame_time: f32, frame_indexes: Iterator<Item = u32>)
|
||||||
|
/// // * `SpriteAnimation` (will be cloned)
|
||||||
|
///
|
||||||
|
/// // The animation is named "soldier_run", with a frame time of 0.1, and the frames
|
||||||
|
/// // 9 to 16 (inclusive) from the atlas.
|
||||||
|
/// ("soldier_run", 0.1, 9..=16),
|
||||||
|
/// ]);
|
||||||
|
/// ```
|
||||||
|
pub fn new<A>(atlas: ResHandle<TextureAtlas>, animations: &[A]) -> Self
|
||||||
|
where
|
||||||
|
A: IntoSpriteAnimation,
|
||||||
|
{
|
||||||
|
let animations = {
|
||||||
|
let atlas = atlas.data_ref().unwrap();
|
||||||
|
|
||||||
|
animations
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| {
|
||||||
|
let a = a.into_animation(&atlas);
|
||||||
|
(a.name.clone(), a)
|
||||||
|
})
|
||||||
|
//.map(|(name, ft, fi)| (name.to_string(), SpriteAnimation::from_atlas(name, *ft, &atlas, fi.clone())))
|
||||||
|
.collect::<HashMap<_, _>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { atlas, animations }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the [`ActiveAtlasAnimation`] for an animation with `name`.
|
||||||
|
///
|
||||||
|
/// > NOTE: this asserts that the animation exists in self in debug builds (uses `debug_assert`).
|
||||||
|
pub fn get_active(&self, name: &str) -> ActiveAtlasAnimation {
|
||||||
|
debug_assert!(
|
||||||
|
self.animations.contains_key(name),
|
||||||
|
"The animation with name '{name}' does not exist!"
|
||||||
|
);
|
||||||
|
|
||||||
|
ActiveAtlasAnimation::new(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lyra_resource::ResourceData for AtlasAnimations {
|
||||||
|
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![self.atlas.untyped_clone()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The active sprite animation from an [`AtlasAnimations`].
|
||||||
|
#[derive(Clone, Component, Reflect)]
|
||||||
|
pub struct ActiveAtlasAnimation {
|
||||||
|
/// The name of the active [`SpriteAnimation`].
|
||||||
|
pub name: String,
|
||||||
|
/// The current frame index in the active [`SpriteAnimation`].
|
||||||
|
///
|
||||||
|
/// This is not the index of the rect in the atlas.
|
||||||
|
pub index: u32,
|
||||||
|
pub paused: bool,
|
||||||
|
/// The time since last animation frame change.
|
||||||
|
///
|
||||||
|
/// This is used to detect if enough time has passed for the frame.
|
||||||
|
timer: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveAtlasAnimation {
|
||||||
|
///Create an [`ActiveAtlasAnimation`].
|
||||||
|
///
|
||||||
|
/// The animation will not be paused.
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
index: 0,
|
||||||
|
paused: false,
|
||||||
|
timer: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an [`ActiveAtlasAnimation`] that starts at a specific point in the animation.
|
||||||
|
///
|
||||||
|
/// The animation will not be paused.
|
||||||
|
pub fn new_at(name: &str, index: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
index,
|
||||||
|
paused: false,
|
||||||
|
timer: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn system_sprite_atlas_animation(
|
||||||
|
mut commands: Commands,
|
||||||
|
dt: Res<DeltaTime>,
|
||||||
|
view: View<(
|
||||||
|
Entities,
|
||||||
|
Option<&mut AtlasSprite>,
|
||||||
|
// support animations from assets or non-asset handles.
|
||||||
|
Or<&AtlasAnimations, &ResHandle<AtlasAnimations>>,
|
||||||
|
&mut ActiveAtlasAnimation,
|
||||||
|
)>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let dt = **dt;
|
||||||
|
|
||||||
|
for (en, mut sprite, animations, mut active) in view.iter() {
|
||||||
|
if active.paused {
|
||||||
|
// Don't touch paused animations
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(animations) = animations.0 {
|
||||||
|
system_animation_entity_impl(
|
||||||
|
&mut commands,
|
||||||
|
dt,
|
||||||
|
en,
|
||||||
|
&mut sprite,
|
||||||
|
&animations,
|
||||||
|
&mut active,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let animations = animations.1.unwrap();
|
||||||
|
if let Some(animations) = animations.data_ref() {
|
||||||
|
system_animation_entity_impl(
|
||||||
|
&mut commands,
|
||||||
|
dt,
|
||||||
|
en,
|
||||||
|
&mut sprite,
|
||||||
|
&animations,
|
||||||
|
&mut active,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn system_animation_entity_impl(
|
||||||
|
commands: &mut Commands,
|
||||||
|
dt: f32,
|
||||||
|
en: Entity,
|
||||||
|
sprite: &mut Option<RefMut<AtlasSprite>>,
|
||||||
|
animations: &AtlasAnimations,
|
||||||
|
active: &mut ActiveAtlasAnimation,
|
||||||
|
) {
|
||||||
|
if let Some(anim) = animations.animations.get(&active.name) {
|
||||||
|
if animations.atlas.is_loaded() {
|
||||||
|
active.timer += dt;
|
||||||
|
|
||||||
|
// Initialize this entity by giving it the first sprite animation frame.
|
||||||
|
if sprite.is_none() {
|
||||||
|
// Get the first sprite in the animation.
|
||||||
|
let rect = anim.frames[active.index as usize];
|
||||||
|
let sprite = AtlasSprite {
|
||||||
|
atlas: animations.atlas.clone(),
|
||||||
|
sprite: rect,
|
||||||
|
};
|
||||||
|
|
||||||
|
commands.insert(en, sprite);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if active.timer >= anim.frame_time {
|
||||||
|
active.timer = 0.0;
|
||||||
|
active.index += 1;
|
||||||
|
|
||||||
|
// wrap the animation around
|
||||||
|
if active.index as usize >= anim.frames.len() {
|
||||||
|
active.index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the sprite for the animation frame
|
||||||
|
let rect = anim.frames[active.index as usize];
|
||||||
|
let new_sprite = AtlasSprite {
|
||||||
|
atlas: animations.atlas.clone(),
|
||||||
|
sprite: rect,
|
||||||
|
};
|
||||||
|
|
||||||
|
let sprite = sprite.as_mut().unwrap();
|
||||||
|
**sprite = new_sprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Unknown active animation: '{}'", active.name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,12 @@ use lyra_reflect::Reflect;
|
||||||
use lyra_resource::ResHandle;
|
use lyra_resource::ResHandle;
|
||||||
use lyra_math::{Vec3, Vec2};
|
use lyra_math::{Vec3, Vec2};
|
||||||
|
|
||||||
|
mod texture_atlas;
|
||||||
|
pub use texture_atlas::*;
|
||||||
|
|
||||||
|
mod animation_sheet;
|
||||||
|
pub use animation_sheet::*;
|
||||||
|
|
||||||
/// How the sprite is positioned and rotated relative to its [`Transform`].
|
/// How the sprite is positioned and rotated relative to its [`Transform`].
|
||||||
///
|
///
|
||||||
/// Default pivot is `Pivot::Center`, this makes it easier to rotate the sprites.
|
/// Default pivot is `Pivot::Center`, this makes it easier to rotate the sprites.
|
||||||
|
@ -45,7 +51,7 @@ impl Pivot {
|
||||||
|
|
||||||
#[derive(Clone, Component, Reflect)]
|
#[derive(Clone, Component, Reflect)]
|
||||||
pub struct Sprite {
|
pub struct Sprite {
|
||||||
pub texture: ResHandle<lyra_resource::Texture>,
|
pub texture: ResHandle<lyra_resource::Image>,
|
||||||
pub color: Vec3,
|
pub color: Vec3,
|
||||||
pub pivot: Pivot,
|
pub pivot: Pivot,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
use glam::UVec2;
|
||||||
|
use lyra_ecs::Component;
|
||||||
|
use lyra_math::URect;
|
||||||
|
use lyra_reflect::Reflect;
|
||||||
|
use lyra_resource::ResHandle;
|
||||||
|
|
||||||
|
/// A texture atlas of multiple sprites.
|
||||||
|
#[derive(Clone, Component, Reflect)]
|
||||||
|
pub struct TextureAtlas {
|
||||||
|
pub texture: ResHandle<lyra_resource::Image>,
|
||||||
|
pub frames: Vec<URect>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureAtlas {
|
||||||
|
/// Create a texture atlas with rectangles of a grid.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `texture` - The asset handle of the texture to get the sprites from.
|
||||||
|
/// * `grid_size` - The number of the cells in the grid (i.e., 9x7 grid).
|
||||||
|
/// * `cell_size` - The dimensions of each cell in the grid (i.e., 100x100 sprites).
|
||||||
|
pub fn from_grid(
|
||||||
|
texture: ResHandle<lyra_resource::Image>,
|
||||||
|
grid_size: UVec2,
|
||||||
|
cell_size: UVec2,
|
||||||
|
) -> Self {
|
||||||
|
let mut frames = vec![];
|
||||||
|
|
||||||
|
for y in 0..grid_size.y {
|
||||||
|
for x in 0..grid_size.x {
|
||||||
|
let r = URect::new(
|
||||||
|
cell_size.x * x,
|
||||||
|
cell_size.y * y,
|
||||||
|
cell_size.x * (x + 1),
|
||||||
|
cell_size.y * (y + 1),
|
||||||
|
);
|
||||||
|
frames.push(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { texture, frames }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lyra_resource::ResourceData for TextureAtlas {
|
||||||
|
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![self.texture.untyped_clone()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sprite from a texture atlas.
|
||||||
|
#[derive(Clone, Component, Reflect)]
|
||||||
|
pub struct AtlasSprite {
|
||||||
|
pub atlas: ResHandle<TextureAtlas>,
|
||||||
|
pub sprite: URect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtlasSprite {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_atlas_index(atlas: ResHandle<TextureAtlas>, i: u32) -> Self {
|
||||||
|
let a = atlas.data_ref().unwrap();
|
||||||
|
let rect = a.frames.get(i as usize).cloned().unwrap(); //index_rect(i);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
atlas: atlas.clone(),
|
||||||
|
sprite: rect,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ enum ModelLoaderError {
|
||||||
|
|
||||||
impl From<ModelLoaderError> for LoaderError {
|
impl From<ModelLoaderError> for LoaderError {
|
||||||
fn from(value: ModelLoaderError) -> Self {
|
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 parent_path = parent_path.display().to_string();
|
||||||
|
|
||||||
let gltf = gltf::Gltf::open(&path)
|
let gltf = gltf::Gltf::open(&path)
|
||||||
.map_err(|ge| LoaderError::DecodingError(ge.into()))?;
|
.map_err(|ge| LoaderError::DecodingError(Arc::new(ge.into())))?;
|
||||||
|
|
||||||
let mut use_bin = false;
|
let mut use_bin = false;
|
||||||
let buffers: Vec<Vec<u8>> = gltf.buffers().flat_map(|b| match b.source() {
|
let buffers: Vec<Vec<u8>> = gltf.buffers().flat_map(|b| match b.source() {
|
||||||
|
@ -290,7 +290,7 @@ mod tests {
|
||||||
|
|
||||||
let manager = ResourceManager::new();
|
let manager = ResourceManager::new();
|
||||||
let gltf = manager.request::<Gltf>(&path).unwrap();
|
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();
|
let gltf = gltf.data_ref().unwrap();
|
||||||
|
|
||||||
assert_eq!(gltf.scenes.len(), 1);
|
assert_eq!(gltf.scenes.len(), 1);
|
||||||
|
|
|
@ -6,4 +6,5 @@ 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]
|
||||||
glam = { version = "0.29.0" }
|
bytemuck = "1.19.0"
|
||||||
|
glam = { version = "0.29.0", features = ["bytemuck"] }
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub fn radians_to_degrees(radians: f32) -> f32 {
|
||||||
radians * 180.0 / PI
|
radians * 180.0 / PI
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Angle {
|
pub enum Angle {
|
||||||
Degrees(f32),
|
Degrees(f32),
|
||||||
Radians(f32),
|
Radians(f32),
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
mod rect;
|
||||||
|
pub use rect::*;
|
|
@ -0,0 +1,90 @@
|
||||||
|
use glam::IVec2;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct IRect {
|
||||||
|
pub min: IVec2,
|
||||||
|
pub max: IVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IRect {
|
||||||
|
pub const ZERO: IRect = IRect { min: IVec2::ZERO, max: IVec2::ZERO };
|
||||||
|
|
||||||
|
pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
min: IVec2::new(x1, y1),
|
||||||
|
max: IVec2::new(x2, y2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_vec(min: IVec2, max: IVec2) -> Self {
|
||||||
|
Self {
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dimensions(&self) -> IVec2 {
|
||||||
|
self.max - self.min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add for IRect {
|
||||||
|
type Output = IRect;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
IRect::from_vec(self.min + rhs.min, self.max + rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::AddAssign for IRect {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
self.min += rhs.min;
|
||||||
|
self.max += rhs.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub for IRect {
|
||||||
|
type Output = IRect;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
IRect::from_vec(self.min - rhs.min, self.max - rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::SubAssign for IRect {
|
||||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
self.min -= rhs.min;
|
||||||
|
self.max -= rhs.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul for IRect {
|
||||||
|
type Output = IRect;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Self) -> Self::Output {
|
||||||
|
IRect::from_vec(self.min * rhs.min, self.max * rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::MulAssign for IRect {
|
||||||
|
fn mul_assign(&mut self, rhs: Self) {
|
||||||
|
self.min *= rhs.min;
|
||||||
|
self.max *= rhs.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Div for IRect {
|
||||||
|
type Output = IRect;
|
||||||
|
|
||||||
|
fn div(self, rhs: Self) -> Self::Output {
|
||||||
|
IRect::from_vec(self.min / rhs.min, self.max / rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DivAssign for IRect {
|
||||||
|
fn div_assign(&mut self, rhs: Self) {
|
||||||
|
self.min /= rhs.min;
|
||||||
|
self.max /= rhs.max;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,17 @@ pub use angle::*;
|
||||||
mod area;
|
mod area;
|
||||||
pub use area::*;
|
pub use area::*;
|
||||||
|
|
||||||
|
mod rect;
|
||||||
|
pub use rect::*;
|
||||||
|
|
||||||
|
#[allow(hidden_glob_reexports)]
|
||||||
|
mod u32;
|
||||||
|
pub use u32::*;
|
||||||
|
|
||||||
|
#[allow(hidden_glob_reexports)]
|
||||||
|
mod i32;
|
||||||
|
pub use i32::*;
|
||||||
|
|
||||||
pub mod transform;
|
pub mod transform;
|
||||||
pub use transform::*;
|
pub use transform::*;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
use glam::Vec2;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct Rect {
|
||||||
|
pub min: Vec2,
|
||||||
|
pub max: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rect {
|
||||||
|
pub const ZERO: Rect = Rect { min: Vec2::ZERO, max: Vec2::ZERO };
|
||||||
|
|
||||||
|
pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
min: Vec2::new(x1, y1),
|
||||||
|
max: Vec2::new(x2, y2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_vec(min: Vec2, max: Vec2) -> Self {
|
||||||
|
Self {
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dimensions(&self) -> Vec2 {
|
||||||
|
(self.max - self.min).abs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add for Rect {
|
||||||
|
type Output = Rect;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Rect::from_vec(self.min + rhs.min, self.max + rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::AddAssign for Rect {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
self.min += rhs.min;
|
||||||
|
self.max += rhs.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub for Rect {
|
||||||
|
type Output = Rect;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
Rect::from_vec(self.min - rhs.min, self.max - rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::SubAssign for Rect {
|
||||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
self.min -= rhs.min;
|
||||||
|
self.max -= rhs.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul for Rect {
|
||||||
|
type Output = Rect;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Self) -> Self::Output {
|
||||||
|
Rect::from_vec(self.min * rhs.min, self.max * rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::MulAssign for Rect {
|
||||||
|
fn mul_assign(&mut self, rhs: Self) {
|
||||||
|
self.min *= rhs.min;
|
||||||
|
self.max *= rhs.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Div for Rect {
|
||||||
|
type Output = Rect;
|
||||||
|
|
||||||
|
fn div(self, rhs: Self) -> Self::Output {
|
||||||
|
Rect::from_vec(self.min / rhs.min, self.max / rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DivAssign for Rect {
|
||||||
|
fn div_assign(&mut self, rhs: Self) {
|
||||||
|
self.min /= rhs.min;
|
||||||
|
self.max /= rhs.max;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
mod rect;
|
||||||
|
pub use rect::*;
|
|
@ -0,0 +1,90 @@
|
||||||
|
use glam::UVec2;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct URect {
|
||||||
|
pub min: UVec2,
|
||||||
|
pub max: UVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl URect {
|
||||||
|
pub const ZERO: URect = URect { min: UVec2::ZERO, max: UVec2::ZERO };
|
||||||
|
|
||||||
|
pub fn new(x1: u32, y1: u32, x2: u32, y2: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
min: UVec2::new(x1, y1),
|
||||||
|
max: UVec2::new(x2, y2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_vec(min: UVec2, max: UVec2) -> Self {
|
||||||
|
Self {
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dimensions(&self) -> UVec2 {
|
||||||
|
self.max - self.min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add for URect {
|
||||||
|
type Output = URect;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
URect::from_vec(self.min + rhs.min, self.max + rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::AddAssign for URect {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
self.min += rhs.min;
|
||||||
|
self.max += rhs.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub for URect {
|
||||||
|
type Output = URect;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
URect::from_vec(self.min - rhs.min, self.max - rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::SubAssign for URect {
|
||||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
self.min -= rhs.min;
|
||||||
|
self.max -= rhs.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul for URect {
|
||||||
|
type Output = URect;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Self) -> Self::Output {
|
||||||
|
URect::from_vec(self.min * rhs.min, self.max * rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::MulAssign for URect {
|
||||||
|
fn mul_assign(&mut self, rhs: Self) {
|
||||||
|
self.min *= rhs.min;
|
||||||
|
self.max *= rhs.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Div for URect {
|
||||||
|
type Output = URect;
|
||||||
|
|
||||||
|
fn div(self, rhs: Self) -> Self::Output {
|
||||||
|
URect::from_vec(self.min / rhs.min, self.max / rhs.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DivAssign for URect {
|
||||||
|
fn div_assign(&mut self, rhs: Self) {
|
||||||
|
self.min /= rhs.min;
|
||||||
|
self.max /= rhs.max;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,17 @@ use crate::{lyra_engine, Enum, Method, Reflect, ReflectMut, ReflectRef};
|
||||||
impl_reflect_simple_struct!(lyra_math::Vec2, fields(x = f32, y = f32));
|
impl_reflect_simple_struct!(lyra_math::Vec2, fields(x = f32, y = f32));
|
||||||
impl_reflect_simple_struct!(lyra_math::Vec3, fields(x = f32, y = f32, z = f32));
|
impl_reflect_simple_struct!(lyra_math::Vec3, fields(x = f32, y = f32, z = f32));
|
||||||
impl_reflect_simple_struct!(lyra_math::Vec4, fields(x = f32, y = f32, z = f32, w = f32));
|
impl_reflect_simple_struct!(lyra_math::Vec4, fields(x = f32, y = f32, z = f32, w = f32));
|
||||||
|
impl_reflect_simple_struct!(lyra_math::UVec2, fields(x = u32, y = u32));
|
||||||
|
impl_reflect_simple_struct!(lyra_math::UVec3, fields(x = u32, y = u32, z = u32));
|
||||||
|
impl_reflect_simple_struct!(lyra_math::UVec4, fields(x = u32, y = u32, z = u32, w = u32));
|
||||||
|
impl_reflect_simple_struct!(lyra_math::IVec2, fields(x = i32, y = i32));
|
||||||
|
impl_reflect_simple_struct!(lyra_math::IVec3, fields(x = i32, y = i32, z = i32));
|
||||||
|
impl_reflect_simple_struct!(lyra_math::IVec4, fields(x = i32, y = i32, z = i32, w = i32));
|
||||||
|
|
||||||
impl_reflect_simple_struct!(lyra_math::Quat, fields(x = f32, y = f32, z = f32, w = f32));
|
impl_reflect_simple_struct!(lyra_math::Quat, fields(x = f32, y = f32, z = f32, w = f32));
|
||||||
|
impl_reflect_simple_struct!(lyra_math::Rect, fields(min = lyra_math::Vec2, max = lyra_math::Vec2));
|
||||||
|
impl_reflect_simple_struct!(lyra_math::URect, fields(min = lyra_math::UVec2, max = lyra_math::UVec2));
|
||||||
|
impl_reflect_simple_struct!(lyra_math::IRect, fields(min = lyra_math::IVec2, max = lyra_math::IVec2));
|
||||||
|
|
||||||
impl_reflect_simple_struct!(
|
impl_reflect_simple_struct!(
|
||||||
lyra_math::Transform,
|
lyra_math::Transform,
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::{loader::LoaderError, ResourceState, UntypedResHandle};
|
use crate::{loader::LoaderError, ResourceState, UntypedResHandle};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -9,7 +7,7 @@ pub enum DependencyState {
|
||||||
/// The resource that had the error.
|
/// The resource that had the error.
|
||||||
handle: UntypedResHandle,
|
handle: UntypedResHandle,
|
||||||
/// The error that the resource ran into when loading.
|
/// The error that the resource ran into when loading.
|
||||||
error: Arc<LoaderError>,
|
error: LoaderError,
|
||||||
},
|
},
|
||||||
Ready,
|
Ready,
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,6 @@ pub mod loader;
|
||||||
mod world_ext;
|
mod world_ext;
|
||||||
pub use world_ext::*;
|
pub use world_ext::*;
|
||||||
|
|
||||||
pub(crate) mod util;
|
|
||||||
|
|
||||||
pub use crossbeam::channel as channel;
|
pub use crossbeam::channel as channel;
|
||||||
pub use notify;
|
pub use notify;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ use super::{LoaderError, PinedBoxLoaderFuture, ResourceLoader};
|
||||||
|
|
||||||
impl From<ImageError> for LoaderError {
|
impl From<ImageError> for LoaderError {
|
||||||
fn from(value: ImageError) -> Self {
|
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>
|
// load the image and construct Resource<Texture>
|
||||||
let image = image::load_from_memory(&buf)
|
let image = image::load_from_memory(&buf)
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
ImageError::IoError(e) => LoaderError::IoError(e),
|
ImageError::IoError(e) => LoaderError::IoError(Arc::new(e)),
|
||||||
_ => LoaderError::DecodingError(e.into()),
|
_ => LoaderError::DecodingError(Arc::new(e.into())),
|
||||||
})?;
|
})?;
|
||||||
let image = Image::from(image);
|
let image = Image::from(image);
|
||||||
let image = Box::new(image) as Box<dyn ResourceData>;
|
let image = Box::new(image) as Box<dyn ResourceData>;
|
||||||
|
@ -89,8 +89,8 @@ impl ResourceLoader for ImageLoader {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
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(Arc::new(e)),
|
||||||
_ => LoaderError::DecodingError(e.into()),
|
_ => LoaderError::DecodingError(Arc::new(e.into())),
|
||||||
})?;
|
})?;
|
||||||
let image = Image::from(image);
|
let image = Image::from(image);
|
||||||
debug!("Finished loading image ({} bytes)", length);
|
debug!("Finished loading image ({} bytes)", length);
|
||||||
|
|
|
@ -7,7 +7,7 @@ use thiserror::Error;
|
||||||
|
|
||||||
use crate::{resource_manager::ResourceStorage, ResourceData, ResourceManager};
|
use crate::{resource_manager::ResourceStorage, ResourceData, ResourceManager};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug, Clone)]
|
||||||
pub enum LoaderError {
|
pub enum LoaderError {
|
||||||
#[error("A malformed path was given: '{0}'")]
|
#[error("A malformed path was given: '{0}'")]
|
||||||
InvalidPath(String),
|
InvalidPath(String),
|
||||||
|
@ -16,16 +16,22 @@ pub enum LoaderError {
|
||||||
UnsupportedExtension(String),
|
UnsupportedExtension(String),
|
||||||
|
|
||||||
#[error("IOError: '{0}'")]
|
#[error("IOError: '{0}'")]
|
||||||
IoError(io::Error),
|
IoError(Arc<io::Error>),
|
||||||
|
|
||||||
// From is implemented for this field in each loader module
|
// From is implemented for this field in each loader module
|
||||||
#[error("Decoding error: '{0}'")]
|
#[error("Decoding error: '{0}'")]
|
||||||
DecodingError(anyhow::Error),
|
DecodingError(Arc<anyhow::Error>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<io::Error> for LoaderError {
|
impl From<io::Error> for LoaderError {
|
||||||
fn from(value: io::Error) -> Self {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ pub trait ResourceData: Send + Sync + Any + 'static {
|
||||||
|
|
||||||
pub enum ResourceState {
|
pub enum ResourceState {
|
||||||
Loading,
|
Loading,
|
||||||
Error(Arc<LoaderError>),
|
Error(LoaderError),
|
||||||
Ready(Box<dyn ResourceData>),
|
Ready(Box<dyn ResourceData>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ impl UntypedResHandle {
|
||||||
matches!(d.state, ResourceState::Ready(_))
|
matches!(d.state, ResourceState::Ready(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_error(&self) -> Option<Arc<LoaderError>> {
|
pub fn get_error(&self) -> Option<LoaderError> {
|
||||||
let d = self.read();
|
let d = self.read();
|
||||||
|
|
||||||
match &d.state {
|
match &d.state {
|
||||||
|
@ -197,14 +197,15 @@ impl UntypedResHandle {
|
||||||
///
|
///
|
||||||
/// This blocks the thread without consuming CPU time; its backed by a
|
/// This blocks the thread without consuming CPU time; its backed by a
|
||||||
/// [`Condvar`](std::sync::Condvar).
|
/// [`Condvar`](std::sync::Condvar).
|
||||||
pub fn wait_for_load(&self) {
|
pub fn wait_for_load(&self) -> Result<(), LoaderError> {
|
||||||
self.wait_for_load_timeout_option_impl(None);
|
self.wait_for_load_timeout_option_impl(None)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does the same as [`UntypedResHandle::wait_for_load`] but has a timeout.
|
/// Does the same as [`UntypedResHandle::wait_for_load`] but has a timeout.
|
||||||
///
|
///
|
||||||
/// Returns true if the resource was loaded before hitting the 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))
|
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
|
/// This blocks the thread without consuming CPU time; its backed by a
|
||||||
/// [`Condvar`](std::sync::Condvar).
|
/// [`Condvar`](std::sync::Condvar).
|
||||||
pub fn wait_recurse_dependencies_load(&self) {
|
pub fn wait_recurse_dependencies_load(&self) -> Result<(), LoaderError> {
|
||||||
self.wait_recurse_dependencies_load_timeout_option_impl(None);
|
self.wait_recurse_dependencies_load_timeout_option_impl(None)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does the same as [`UntypedResHandle::wait_recurse_dependencies_load`] but has a timeout.
|
/// Does the same as [`UntypedResHandle::wait_recurse_dependencies_load`] but has a timeout.
|
||||||
///
|
///
|
||||||
/// Returns true if the resource was loaded before hitting the 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))
|
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();
|
let d = self.read();
|
||||||
|
|
||||||
if matches!(d.state, ResourceState::Ready(_)) {
|
if matches!(d.state, ResourceState::Ready(_)) {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let cv = d.condvar.clone();
|
let cv = d.condvar.clone();
|
||||||
|
// MUST DROP to avoid deadlock
|
||||||
drop(d);
|
drop(d);
|
||||||
|
|
||||||
let l = cv.0.lock().unwrap();
|
let l = cv.0.lock().unwrap();
|
||||||
|
|
||||||
if let Some(timeout) = timeout {
|
if let Some(timeout) = timeout {
|
||||||
let (_unused, timeout) = cv.1.wait_timeout(l, timeout).unwrap();
|
let (_unused, _) = cv.1.wait_timeout(l, timeout).unwrap();
|
||||||
!timeout.timed_out()
|
|
||||||
} else {
|
} else {
|
||||||
let _unused = cv.1.wait(l).unwrap();
|
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 {
|
fn wait_recurse_dependencies_load_timeout_option_impl(&self, timeout: Option<Duration>) -> Result<bool, LoaderError> {
|
||||||
if !self.wait_for_load_timeout_option_impl(timeout) {
|
if !self.wait_for_load_timeout_option_impl(timeout)? {
|
||||||
return false;
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = self.read();
|
let res = self.read();
|
||||||
|
@ -257,13 +265,13 @@ impl UntypedResHandle {
|
||||||
// waiting for some resources and finish early.
|
// waiting for some resources and finish early.
|
||||||
while self.recurse_dependency_state().is_loading() {
|
while self.recurse_dependency_state().is_loading() {
|
||||||
for dep in data.recur_dependencies() {
|
for dep in data.recur_dependencies() {
|
||||||
if !dep.wait_for_load_timeout_option_impl(timeout) {
|
if !dep.wait_for_load_timeout_option_impl(timeout)? {
|
||||||
return false;
|
return Ok(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
Ok(true)
|
||||||
},
|
},
|
||||||
// self.wait_for_load at the start ensures that the state is ready
|
// self.wait_for_load at the start ensures that the state is ready
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
|
@ -315,6 +323,12 @@ pub struct ResHandle<T: ResourceData> {
|
||||||
_marker: PhantomData<T>,
|
_marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: ResourceData> Default for ResHandle<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_loading(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: ResourceData> Clone for ResHandle<T> {
|
impl<T: ResourceData> Clone for ResHandle<T> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -591,7 +605,7 @@ mod tests {
|
||||||
assert!(state.is_loading());
|
assert!(state.is_loading());
|
||||||
|
|
||||||
// this will take a bit
|
// this will take a bit
|
||||||
res.wait_recurse_dependencies_load();
|
res.wait_recurse_dependencies_load().unwrap();
|
||||||
|
|
||||||
let state = res.recurse_dependency_state();
|
let state = res.recurse_dependency_state();
|
||||||
assert!(!state.is_loading());
|
assert!(!state.is_loading());
|
||||||
|
|
|
@ -166,7 +166,8 @@ impl ResourceManager {
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let mut d = untyped.write();
|
let mut d = untyped.write();
|
||||||
d.state = ResourceState::Error(Arc::new(err));
|
d.state = ResourceState::Error(err);
|
||||||
|
d.condvar.1.notify_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -183,6 +184,14 @@ impl ResourceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store a new resource, returning its handle.
|
||||||
|
pub fn store_new<T: ResourceData>(&self, data: T) -> ResHandle<T> {
|
||||||
|
let handle = ResHandle::new_ready(None, data);
|
||||||
|
let mut state = self.state_mut();
|
||||||
|
state.resources.insert(handle.uuid().to_string(), Arc::new(handle.clone()));
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
|
||||||
/// Store a resource using its uuid.
|
/// Store a resource using its uuid.
|
||||||
///
|
///
|
||||||
/// The resource cannot be requested with [`ResourceManager::request`], it can only be
|
/// The resource cannot be requested with [`ResourceManager::request`], it can only be
|
||||||
|
@ -236,10 +245,12 @@ impl ResourceManager {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
let mut d = thand.write();
|
let mut d = thand.write();
|
||||||
d.state = ResourceState::Ready(data);
|
d.state = ResourceState::Ready(data);
|
||||||
|
d.condvar.1.notify_all();
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let mut d = thand.write();
|
let mut d = thand.write();
|
||||||
d.state = ResourceState::Error(Arc::new(err));
|
d.state = ResourceState::Error(err);
|
||||||
|
d.condvar.1.notify_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -273,6 +284,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
|
/// 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>>>> {
|
pub fn watch(&self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
|
||||||
let (send, recv) = crossbeam::channel::bounded(15);
|
let (send, recv) = crossbeam::channel::bounded(15);
|
||||||
|
@ -358,7 +378,7 @@ impl ResourceManager {
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let mut d = thand.write();
|
let mut d = thand.write();
|
||||||
d.state = ResourceState::Error(Arc::new(err));
|
d.state = ResourceState::Error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -370,7 +390,7 @@ impl ResourceManager {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use std::{io, ops::Deref};
|
use std::io;
|
||||||
|
|
||||||
use instant::Instant;
|
use instant::Instant;
|
||||||
|
|
||||||
|
@ -420,8 +440,7 @@ pub mod tests {
|
||||||
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
|
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
|
||||||
assert!(!res.is_loaded());
|
assert!(!res.is_loaded());
|
||||||
|
|
||||||
res.wait_for_load();
|
res.wait_for_load().unwrap();
|
||||||
//busy_wait_resource(&res, 10.0);
|
|
||||||
|
|
||||||
// shouldn't panic because of the loop
|
// shouldn't panic because of the loop
|
||||||
res.data_ref().unwrap();
|
res.data_ref().unwrap();
|
||||||
|
@ -455,7 +474,7 @@ pub mod tests {
|
||||||
// make sure the error is NotFound
|
// make sure the error is NotFound
|
||||||
//RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true,
|
//RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true,
|
||||||
ResourceState::Error(err) => {
|
ResourceState::Error(err) => {
|
||||||
match err.deref() {
|
match err {
|
||||||
LoaderError::IoError(e) if e.kind() == io::ErrorKind::NotFound => true,
|
LoaderError::IoError(e) if e.kind() == io::ErrorKind::NotFound => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
use base64::Engine;
|
|
|
@ -17,7 +17,8 @@ impl ScriptApiProvider for LyraEcsApiProvider {
|
||||||
world.register_lua_wrapper::<LuaActionHandler>();
|
world.register_lua_wrapper::<LuaActionHandler>();
|
||||||
world.register_lua_wrapper::<LuaWindow>();
|
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::<LuaFreeFlyCamera>("FreeFlyCamera");
|
||||||
world.register_lua_convert_component::<LuaWorldTransform>("WorldTransform");
|
world.register_lua_convert_component::<LuaWorldTransform>("WorldTransform");
|
||||||
|
|
||||||
|
@ -54,7 +55,6 @@ impl ScriptApiProvider for LyraEcsApiProvider {
|
||||||
globals.set("TickOfQuery", ctx.create_proxy::<LuaTickOfQuery>()?)?;
|
globals.set("TickOfQuery", ctx.create_proxy::<LuaTickOfQuery>()?)?;
|
||||||
globals.set("OptionalQuery", ctx.create_proxy::<LuaOptionalQuery>()?)?;
|
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::<LuaFreeFlyCamera>(&ctx, &globals, "FreeFlyCamera")?;
|
||||||
expose_comp_table_wrapper::<LuaWorldTransform>(&ctx, &globals, "WorldTransform")?;
|
expose_comp_table_wrapper::<LuaWorldTransform>(&ctx, &globals, "WorldTransform")?;
|
||||||
expose_table_wrapper::<LuaDeviceEvent>(&ctx, &globals, "DeviceEvent")?;
|
expose_table_wrapper::<LuaDeviceEvent>(&ctx, &globals, "DeviceEvent")?;
|
||||||
|
|
|
@ -1,19 +1,66 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
lua::{
|
lua::{
|
||||||
wrappers::{LuaAngle, LuaTransform},
|
wrappers::LuaAngle,
|
||||||
LuaWrapper
|
LuaWrapper,
|
||||||
},
|
},
|
||||||
ScriptBorrow,
|
ScriptBorrow,
|
||||||
};
|
};
|
||||||
use lyra_game::{render::camera::CameraProjectionMode, scene::CameraComponent};
|
use lyra_game::{
|
||||||
use lyra_reflect::Enum;
|
render::camera::{CameraProjectionMode, OrthographicProjection, PerspectiveProjection},
|
||||||
|
scene::CameraComponent,
|
||||||
|
};
|
||||||
use lyra_scripting_derive::to_lua_convert;
|
use lyra_scripting_derive::to_lua_convert;
|
||||||
|
|
||||||
fn projection_mode_from_str(s: &str) -> Option<CameraProjectionMode> {
|
fn projection_as_table(lua: &mlua::Lua, projection: CameraProjectionMode) -> mlua::Result<mlua::Table> {
|
||||||
match s {
|
let table = lua.create_table()?;
|
||||||
"perspective" => Some(CameraProjectionMode::Perspective),
|
|
||||||
"orthographic" => Some(CameraProjectionMode::Orthographic),
|
match projection {
|
||||||
_ => None,
|
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'
|
// Reflection type, can be 'component' or 'resource'
|
||||||
reflect=component,
|
reflect=component,
|
||||||
fields={
|
fields={
|
||||||
transform: wrap(LuaTransform),
|
|
||||||
fov: wrap(LuaAngle),
|
|
||||||
(
|
(
|
||||||
mode,
|
projection,
|
||||||
// Get the table from the value, result must be `CameraProjectionMode`
|
|
||||||
get={
|
get={
|
||||||
let mode: String = table.get("mode")?;
|
let p: mlua::Table = table.get("projection")?;
|
||||||
projection_mode_from_str(&mode).unwrap()
|
projection_from_table(&p)?
|
||||||
|
|
||||||
},
|
},
|
||||||
// Get the value from self, result must be the type in Lua, here its `String`.
|
|
||||||
set={
|
set={
|
||||||
self.mode.variant_name().to_lowercase()
|
projection_as_table(lua, self.projection)?
|
||||||
}
|
},
|
||||||
),
|
)
|
||||||
debug: bool
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::{lua::LuaWrapper, ScriptBorrow};
|
use crate::{lua::LuaWrapper, ScriptBorrow};
|
||||||
use lyra_game::scene::FreeFlyCamera;
|
use lyra_game::scene::FreeFly3dCamera;
|
||||||
use lyra_scripting_derive::to_lua_convert;
|
use lyra_scripting_derive::to_lua_convert;
|
||||||
|
|
||||||
to_lua_convert!(
|
to_lua_convert!(
|
||||||
// Struct that is being wrapped
|
// Struct that is being wrapped
|
||||||
FreeFlyCamera,
|
FreeFly3dCamera,
|
||||||
// Reflection type, can be 'component' or 'resource'
|
// Reflection type, can be 'component' or 'resource'
|
||||||
reflect=component,
|
reflect=component,
|
||||||
fields={
|
fields={
|
||||||
|
|
|
@ -13,9 +13,6 @@ pub use delta_time::*;
|
||||||
mod window;
|
mod window;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
||||||
mod camera;
|
|
||||||
pub use camera::*;
|
|
||||||
|
|
||||||
mod free_fly_camera;
|
mod free_fly_camera;
|
||||||
pub use free_fly_camera::*;
|
pub use free_fly_camera::*;
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 70daf320827f64b325a77718df07177d74d7ea58
|
Subproject commit 76a1c98abd375dbaca694f9728e3d6c105ef3f1b
|
|
@ -1,13 +1,24 @@
|
||||||
use lyra_engine::{
|
use lyra_engine::{
|
||||||
assets::ResourceManager, gltf::Gltf, ecs::query::View, game::App, input::{
|
assets::{Image, ResourceManager},
|
||||||
|
ecs::query::{Res, ResMut, View},
|
||||||
|
game::App,
|
||||||
|
gltf::Gltf,
|
||||||
|
input::{
|
||||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||||
InputActionPlugin, KeyCode, LayoutId,
|
InputActionPlugin, KeyCode, LayoutId,
|
||||||
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{
|
},
|
||||||
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
|
math::{self, Rect, Transform, URect, UVec2, Vec2, Vec3},
|
||||||
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
|
render::light::directional::DirectionalLight,
|
||||||
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
scene::{
|
||||||
}, winit::WindowOptions
|
system_update_world_transforms, Camera2dBundle, CameraProjection, OrthographicProjection,
|
||||||
|
ScaleMode, TopDown2dCamera, TopDown2dCameraPlugin, 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, AtlasAnimations, AtlasSprite, Pivot, Sprite, SpriteAnimation, TextureAtlas},
|
||||||
|
DeltaTime,
|
||||||
};
|
};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -85,11 +96,24 @@ async fn main() {
|
||||||
.with_plugin(setup_scene_plugin)
|
.with_plugin(setup_scene_plugin)
|
||||||
.with_plugin(action_handler_plugin)
|
.with_plugin(action_handler_plugin)
|
||||||
//.with_plugin(camera_debug_plugin)
|
//.with_plugin(camera_debug_plugin)
|
||||||
.with_plugin(FreeFlyCameraPlugin);
|
.with_plugin(TopDown2dCameraPlugin)
|
||||||
|
.with_system(
|
||||||
|
"system_update_world_transforms",
|
||||||
|
system_update_world_transforms,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
a.run();
|
a.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_scene_plugin(app: &mut App) {
|
fn setup_scene_plugin(app: &mut App) {
|
||||||
|
//app.add_resource(Timer(0.0));
|
||||||
|
//app.with_system("sprite_change", sprite_change, &[]);
|
||||||
|
app.with_system(
|
||||||
|
"sprite_atlas_animation",
|
||||||
|
sprite::system_sprite_atlas_animation,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
let world = &mut app.world;
|
let world = &mut app.world;
|
||||||
let resman = world.get_resource_mut::<ResourceManager>().unwrap();
|
let resman = world.get_resource_mut::<ResourceManager>().unwrap();
|
||||||
|
|
||||||
|
@ -111,14 +135,35 @@ fn setup_scene_plugin(app: &mut App) {
|
||||||
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
|
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
cube_gltf.wait_recurse_dependencies_load();
|
cube_gltf.wait_recurse_dependencies_load().unwrap();
|
||||||
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
|
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
|
||||||
|
|
||||||
|
let image = resman.request::<Image>("../assets/Egg_item.png").unwrap();
|
||||||
|
image.wait_recurse_dependencies_load().unwrap();
|
||||||
|
|
||||||
|
let soldier = resman
|
||||||
|
.request::<Image>(
|
||||||
|
"../assets/tiny_rpg_characters/Characters(100x100)/Soldier/Soldier/Soldier.png",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
soldier.wait_recurse_dependencies_load().unwrap();
|
||||||
|
|
||||||
|
let atlas = resman.store_new(TextureAtlas::from_grid(
|
||||||
|
soldier,
|
||||||
|
UVec2::new(9, 7),
|
||||||
|
UVec2::new(100, 100),
|
||||||
|
));
|
||||||
|
let animations = AtlasAnimations::new(atlas, &[("soldier_run", 0.1, 9..=16)]);
|
||||||
|
let run_anim = animations.get_active("soldier_run");
|
||||||
|
let animations = resman.store_new(animations);
|
||||||
|
|
||||||
drop(resman);
|
drop(resman);
|
||||||
|
|
||||||
world.spawn((
|
world.spawn((
|
||||||
cube_mesh.clone(),
|
animations,
|
||||||
|
run_anim,
|
||||||
WorldTransform::default(),
|
WorldTransform::default(),
|
||||||
Transform::from_xyz(0.0, 0.0, -2.0),
|
Transform::from_xyz(0.0, 0.0, -10.0),
|
||||||
));
|
));
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -136,7 +181,19 @@ fn setup_scene_plugin(app: &mut App) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut camera = CameraComponent::new_2d();
|
world.spawn((
|
||||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
Camera2dBundle {
|
||||||
world.spawn((camera, FreeFlyCamera::default()));
|
projection: CameraProjection::Orthographic(OrthographicProjection {
|
||||||
|
scale_mode: ScaleMode::Height(180.0),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||||
|
TopDown2dCamera {
|
||||||
|
zoom_speed: Some(0.2),
|
||||||
|
speed: 14.0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Egg_item.png
|
|
@ -0,0 +1,3 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!source.txt
|
|
@ -0,0 +1 @@
|
||||||
|
https://zerie.itch.io/tiny-rpg-character-asset-pack
|
|
@ -1,25 +1,16 @@
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use lyra_engine::{
|
use lyra_engine::{
|
||||||
assets::ResourceManager, gltf::Gltf,
|
assets::ResourceManager, ecs::{
|
||||||
ecs::{
|
|
||||||
query::{Res, View},
|
query::{Res, View},
|
||||||
system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem},
|
system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem},
|
||||||
World,
|
World,
|
||||||
},
|
}, game::App, gltf::Gltf, input::{
|
||||||
game::App,
|
|
||||||
input::{
|
|
||||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||||
},
|
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{
|
||||||
math::{self, Transform, Vec3},
|
CameraBundle, FreeFly3dCamera, 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
|
||||||
render::light::directional::DirectionalLight,
|
}, DeltaTime
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
@ -135,9 +126,11 @@ fn setup_scene_plugin(app: &mut App) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut camera = CameraComponent::new_3d();
|
world.spawn((
|
||||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 1.5);
|
CameraBundle::default(),
|
||||||
world.spawn((camera, FreeFlyCamera::default()));
|
Transform::from_xyz(0.0, 0.0, 1.5),
|
||||||
|
FreeFly3dCamera::default(),
|
||||||
|
));
|
||||||
|
|
||||||
let fps_counter = |counter: Res<fps_counter::FPSCounter>,
|
let fps_counter = |counter: Res<fps_counter::FPSCounter>,
|
||||||
delta: Res<DeltaTime>| -> anyhow::Result<()> {
|
delta: Res<DeltaTime>| -> anyhow::Result<()> {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use lyra_engine::{
|
use lyra_engine::{
|
||||||
assets::ResourceManager, gltf::Gltf, game::App, input::{
|
assets::ResourceManager, game::App, gltf::Gltf, input::{
|
||||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||||
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{
|
}, 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, FreeFly3dCamera, 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}
|
}, script::{lua::{LuaScript, LuaScriptingPlugin}, Script, ScriptList}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,9 +125,11 @@ fn setup_scene_plugin(app: &mut App) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut camera = CameraComponent::new_3d();
|
world.spawn((
|
||||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
CameraBundle::default(),
|
||||||
world.spawn((camera, FreeFlyCamera::default()));
|
Transform::from_xyz(0.0, 0.0, 5.5),
|
||||||
|
FreeFly3dCamera::default(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_script_plugin(app: &mut App) {
|
fn setup_script_plugin(app: &mut App) {
|
||||||
|
|
|
@ -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, FreeFly3dCamera, 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 rand::Rng;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
@ -95,7 +95,6 @@ async fn main() {
|
||||||
app.with_plugin(lyra_engine::DefaultPlugins);
|
app.with_plugin(lyra_engine::DefaultPlugins);
|
||||||
app.with_plugin(setup_scene_plugin);
|
app.with_plugin(setup_scene_plugin);
|
||||||
app.with_plugin(action_handler_plugin);
|
app.with_plugin(action_handler_plugin);
|
||||||
app.with_plugin(camera_debug_plugin);
|
|
||||||
app.with_plugin(FreeFlyCameraPlugin);
|
app.with_plugin(FreeFlyCameraPlugin);
|
||||||
app.run();
|
app.run();
|
||||||
}
|
}
|
||||||
|
@ -184,26 +183,15 @@ fn setup_scene_plugin(app: &mut App) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut camera = CameraComponent::new_3d();
|
world.spawn((
|
||||||
// these values were taken by manually positioning the camera in the scene.
|
CameraBundle::default(),
|
||||||
camera.transform = Transform::new(
|
// these values were taken by manually positioning the camera in the scene
|
||||||
Vec3::new(-10.0, 0.94, -0.28),
|
// and printing the position to console.
|
||||||
Quat::from_xyzw(0.03375484, -0.7116095, 0.0342693, 0.70092666),
|
Transform::new(
|
||||||
Vec3::ONE
|
Vec3::new(-10.0, 0.94, -0.28),
|
||||||
);
|
Quat::from_xyzw(0.03375484, -0.7116095, 0.0342693, 0.70092666),
|
||||||
world.spawn(( camera, FreeFlyCamera::default() ));
|
Vec3::ONE,
|
||||||
}
|
),
|
||||||
|
FreeFly3dCamera::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, &[]);
|
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use lyra_engine::{
|
use lyra_engine::{
|
||||||
assets::ResourceManager, gltf::Gltf,
|
assets::ResourceManager,
|
||||||
game::App,
|
game::App,
|
||||||
|
gltf::Gltf,
|
||||||
input::{
|
input::{
|
||||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||||
|
@ -11,9 +12,9 @@ use lyra_engine::{
|
||||||
light::{directional::DirectionalLight, SpotLight},
|
light::{directional::DirectionalLight, SpotLight},
|
||||||
},
|
},
|
||||||
scene::{
|
scene::{
|
||||||
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
|
CameraBundle, FreeFly3dCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT,
|
||||||
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
|
ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD,
|
||||||
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
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),
|
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
|
let palm_tree_platform_gltf = resman
|
||||||
.request::<Gltf>("../assets/shadows-platform-palmtree.glb")
|
.request::<Gltf>("../assets/shadows-platform-palmtree.glb")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -214,8 +208,12 @@ fn setup_scene_plugin(app: &mut App) {
|
||||||
Vec3::new(4.0 - 1.43, -13.0, 0.0),
|
Vec3::new(4.0 - 1.43, -13.0, 0.0),
|
||||||
//Vec3::new(-5.0, 1.0, -0.28),
|
//Vec3::new(-5.0, 1.0, -0.28),
|
||||||
//Vec3::new(-10.0, 0.94, -0.28),
|
//Vec3::new(-10.0, 0.94, -0.28),
|
||||||
|
Quat::from_euler(
|
||||||
Quat::from_euler(math::EulerRot::XYZ, 0.0, math::Angle::Degrees(-45.0).to_radians(), 0.0),
|
math::EulerRot::XYZ,
|
||||||
|
0.0,
|
||||||
|
math::Angle::Degrees(-45.0).to_radians(),
|
||||||
|
0.0,
|
||||||
|
),
|
||||||
Vec3::new(0.15, 0.15, 0.15),
|
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();
|
let mut pos = Transform::from_xyz(-1.0, -10.0, -1.5);
|
||||||
camera.transform.translation = math::Vec3::new(-1.0, -10.0, -1.5);
|
pos.rotate_x(math::Angle::Degrees(-27.0));
|
||||||
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
|
pos.rotate_y(math::Angle::Degrees(-90.0));
|
||||||
camera.transform.rotate_y(math::Angle::Degrees(-90.0));
|
world.spawn((CameraBundle::default(), pos, FreeFly3dCamera::default()));
|
||||||
|
|
||||||
world.spawn((camera, FreeFlyCamera::default()));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
use lyra_engine::{
|
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,
|
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||||
InputActionPlugin, KeyCode, LayoutId,
|
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
|
math::{self, Transform, Vec3},
|
||||||
}, winit::WindowOptions
|
render::light::directional::DirectionalLight,
|
||||||
|
scene::{
|
||||||
|
system_update_world_transforms, CameraBundle, FreeFly3dCamera, 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]
|
#[async_std::main]
|
||||||
|
@ -84,7 +92,11 @@ async fn main() {
|
||||||
.with_plugin(action_handler_plugin)
|
.with_plugin(action_handler_plugin)
|
||||||
//.with_plugin(camera_debug_plugin)
|
//.with_plugin(camera_debug_plugin)
|
||||||
.with_plugin(FreeFlyCameraPlugin)
|
.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();
|
a.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +147,9 @@ fn setup_scene_plugin(app: &mut App) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut camera = CameraComponent::new_3d();
|
world.spawn((
|
||||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
CameraBundle::default(),
|
||||||
world.spawn((camera, FreeFlyCamera::default()));
|
Transform::from_xyz(0.0, 0.0, 5.5),
|
||||||
|
FreeFly3dCamera::default(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use lyra_engine::assets::ResourceManager;
|
use lyra_engine::assets::ResourceManager;
|
||||||
|
use lyra_engine::scene::CameraBundle;
|
||||||
use lyra_engine::{
|
use lyra_engine::{
|
||||||
gltf::Gltf,
|
|
||||||
ecs::{
|
ecs::{
|
||||||
query::{Res, View},
|
system::{Criteria, CriteriaSchedule},
|
||||||
system::{Criteria, CriteriaSchedule, IntoSystem},
|
|
||||||
Component, World,
|
Component, World,
|
||||||
},
|
},
|
||||||
game::App,
|
game::App,
|
||||||
|
gltf::Gltf,
|
||||||
input::{
|
input::{
|
||||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||||
|
@ -16,9 +16,9 @@ use lyra_engine::{
|
||||||
math::{self, Quat, Transform, Vec3},
|
math::{self, Quat, Transform, Vec3},
|
||||||
render::light::{directional::DirectionalLight, PointLight, SpotLight},
|
render::light::{directional::DirectionalLight, PointLight, SpotLight},
|
||||||
scene::{
|
scene::{
|
||||||
self, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
|
self, FreeFly3dCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT,
|
||||||
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
|
ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD,
|
||||||
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
||||||
},
|
},
|
||||||
DeltaTime,
|
DeltaTime,
|
||||||
};
|
};
|
||||||
|
@ -101,205 +101,6 @@ struct CubeFlag;
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let setup_sys = |world: &mut World| -> anyhow::Result<()> {
|
|
||||||
{
|
|
||||||
/* let mut window_options = world.get_resource_mut::<Ct<WindowOptions>>();
|
|
||||||
window_options.cursor_grab = CursorGrabMode::Confined;
|
|
||||||
window_options.cursor_visible = false; */
|
|
||||||
}
|
|
||||||
|
|
||||||
let resman = world.get_resource_mut::<ResourceManager>().unwrap();
|
|
||||||
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").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_gltf = resman
|
|
||||||
.request::<Gltf>("../assets/texture-sep/texture-sep.gltf")
|
|
||||||
.unwrap();
|
|
||||||
/*let crate_gltf = resman.request::<Gltf>("assets/crate/crate.gltf").unwrap();
|
|
||||||
|
|
||||||
let separate_gltf = resman.request::<Gltf>("assets/pos-testing/child-node-cubes.glb").unwrap(); */
|
|
||||||
//drop(resman);
|
|
||||||
|
|
||||||
cube_gltf.wait_recurse_dependencies_load();
|
|
||||||
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);
|
|
||||||
|
|
||||||
sponza_model.wait_recurse_dependencies_load();
|
|
||||||
let sponza_scene = &sponza_model.data_ref().unwrap().scenes[0];
|
|
||||||
|
|
||||||
world.spawn((
|
|
||||||
sponza_scene.clone(),
|
|
||||||
WorldTransform::default(),
|
|
||||||
Transform::from_xyz(0.0, 0.0, 0.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);
|
|
||||||
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
|
||||||
light_tran.rotate_y(math::Angle::Degrees(25.0));
|
|
||||||
world.spawn((
|
|
||||||
DirectionalLight {
|
|
||||||
enabled: true,
|
|
||||||
color: Vec3::ONE,
|
|
||||||
intensity: 0.35, //..Default::default()
|
|
||||||
},
|
|
||||||
light_tran,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let t = Transform::new(
|
|
||||||
//Vec3::new(-5.0, 1.0, -1.28),
|
|
||||||
Vec3::new(-5.0, 1.0, -0.0),
|
|
||||||
//Vec3::new(-10.0, 0.94, -0.28),
|
|
||||||
Quat::IDENTITY,
|
|
||||||
Vec3::new(0.25, 0.25, 0.25),
|
|
||||||
);
|
|
||||||
|
|
||||||
world.spawn((
|
|
||||||
PointLight {
|
|
||||||
enabled: true,
|
|
||||||
color: Vec3::new(0.0, 0.0, 1.0),
|
|
||||||
intensity: 1.0,
|
|
||||||
range: 2.0,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
WorldTransform::from(t),
|
|
||||||
t,
|
|
||||||
cube_mesh.clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let t = Transform::new(
|
|
||||||
Vec3::new(-3.0, 0.2, -1.5),
|
|
||||||
//Vec3::new(-5.0, 1.0, -0.28),
|
|
||||||
//Vec3::new(-10.0, 0.94, -0.28),
|
|
||||||
Quat::IDENTITY,
|
|
||||||
Vec3::new(0.15, 0.15, 0.15),
|
|
||||||
);
|
|
||||||
|
|
||||||
world.spawn((
|
|
||||||
PointLight {
|
|
||||||
enabled: true,
|
|
||||||
color: Vec3::new(0.0, 0.5, 1.0),
|
|
||||||
intensity: 1.0,
|
|
||||||
range: 1.0,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
WorldTransform::from(t),
|
|
||||||
t,
|
|
||||||
cube_mesh.clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let t = Transform::new(
|
|
||||||
Vec3::new(0.0, 0.2, -1.5),
|
|
||||||
//Vec3::new(-5.0, 1.0, -0.28),
|
|
||||||
//Vec3::new(-10.0, 0.94, -0.28),
|
|
||||||
Quat::IDENTITY,
|
|
||||||
Vec3::new(0.15, 0.15, 0.15),
|
|
||||||
);
|
|
||||||
|
|
||||||
world.spawn((
|
|
||||||
SpotLight {
|
|
||||||
enabled: true,
|
|
||||||
color: Vec3::new(1.0, 0.0, 0.0),
|
|
||||||
intensity: 1.0,
|
|
||||||
range: 1.5,
|
|
||||||
//cutoff: math::Angle::Degrees(45.0),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
WorldTransform::from(t),
|
|
||||||
t,
|
|
||||||
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);
|
|
||||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
|
||||||
world.spawn((
|
|
||||||
PointLight {
|
|
||||||
color: Vec3::new(1.0, 1.0, 1.0),
|
|
||||||
|
|
||||||
intensity: 1.0,
|
|
||||||
|
|
||||||
constant: 1.0,
|
|
||||||
linear: 0.045,
|
|
||||||
quadratic: 0.0075,
|
|
||||||
|
|
||||||
ambient: 0.1,
|
|
||||||
diffuse: 1.0,
|
|
||||||
specular: 1.3,
|
|
||||||
},
|
|
||||||
TransformComponent::from(light_tran),
|
|
||||||
ModelComponent(cube_model.clone()),
|
|
||||||
));
|
|
||||||
} */
|
|
||||||
|
|
||||||
let mut camera = CameraComponent::new_3d();
|
|
||||||
// these values were taken by manually positioning the camera in the scene.
|
|
||||||
camera.transform = 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()));
|
|
||||||
|
|
||||||
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_plugin = |app: &mut App| {
|
||||||
let action_handler = ActionHandler::builder()
|
let action_handler = ActionHandler::builder()
|
||||||
.add_layout(LayoutId::from(0))
|
.add_layout(LayoutId::from(0))
|
||||||
|
@ -388,11 +189,197 @@ async fn main() {
|
||||||
|
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
app.with_plugin(lyra_engine::DefaultPlugins);
|
app.with_plugin(lyra_engine::DefaultPlugins);
|
||||||
app.with_startup_system(setup_sys.into_system());
|
app.with_plugin(setup_scene_plugin);
|
||||||
app.with_plugin(action_handler_plugin);
|
app.with_plugin(action_handler_plugin);
|
||||||
//app.with_plugin(script_test_plugin)
|
//app.with_plugin(script_test_plugin)
|
||||||
//app.with_plugin(fps_plugin)
|
//app.with_plugin(fps_plugin)
|
||||||
app.with_plugin(camera_debug_plugin);
|
app.with_system(
|
||||||
|
"update_world_transforms",
|
||||||
|
scene::system_update_world_transforms,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
app.with_plugin(FreeFlyCameraPlugin);
|
app.with_plugin(FreeFlyCameraPlugin);
|
||||||
app.run();
|
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;
|
||||||
|
window_options.cursor_visible = false; */
|
||||||
|
}
|
||||||
|
|
||||||
|
let resman = world.get_resource_mut::<ResourceManager>().unwrap();
|
||||||
|
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").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_gltf = resman
|
||||||
|
.request::<Gltf>("../assets/texture-sep/texture-sep.gltf")
|
||||||
|
.unwrap();
|
||||||
|
/*let crate_gltf = resman.request::<Gltf>("assets/crate/crate.gltf").unwrap();
|
||||||
|
|
||||||
|
let separate_gltf = resman.request::<Gltf>("assets/pos-testing/child-node-cubes.glb").unwrap(); */
|
||||||
|
//drop(resman);
|
||||||
|
|
||||||
|
cube_gltf.wait_recurse_dependencies_load();
|
||||||
|
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);
|
||||||
|
|
||||||
|
sponza_model.wait_recurse_dependencies_load();
|
||||||
|
let sponza_scene = &sponza_model.data_ref().unwrap().scenes[0];
|
||||||
|
|
||||||
|
world.spawn((
|
||||||
|
sponza_scene.clone(),
|
||||||
|
WorldTransform::default(),
|
||||||
|
Transform::from_xyz(0.0, 0.0, 0.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);
|
||||||
|
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
||||||
|
light_tran.rotate_y(math::Angle::Degrees(25.0));
|
||||||
|
world.spawn((
|
||||||
|
DirectionalLight {
|
||||||
|
enabled: true,
|
||||||
|
color: Vec3::ONE,
|
||||||
|
intensity: 0.35, //..Default::default()
|
||||||
|
},
|
||||||
|
light_tran,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let t = Transform::new(
|
||||||
|
//Vec3::new(-5.0, 1.0, -1.28),
|
||||||
|
Vec3::new(-5.0, 1.0, -0.0),
|
||||||
|
//Vec3::new(-10.0, 0.94, -0.28),
|
||||||
|
Quat::IDENTITY,
|
||||||
|
Vec3::new(0.25, 0.25, 0.25),
|
||||||
|
);
|
||||||
|
|
||||||
|
world.spawn((
|
||||||
|
PointLight {
|
||||||
|
enabled: true,
|
||||||
|
color: Vec3::new(0.0, 0.0, 1.0),
|
||||||
|
intensity: 1.0,
|
||||||
|
range: 2.0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
WorldTransform::from(t),
|
||||||
|
t,
|
||||||
|
cube_mesh.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let t = Transform::new(
|
||||||
|
Vec3::new(-3.0, 0.2, -1.5),
|
||||||
|
//Vec3::new(-5.0, 1.0, -0.28),
|
||||||
|
//Vec3::new(-10.0, 0.94, -0.28),
|
||||||
|
Quat::IDENTITY,
|
||||||
|
Vec3::new(0.15, 0.15, 0.15),
|
||||||
|
);
|
||||||
|
|
||||||
|
world.spawn((
|
||||||
|
PointLight {
|
||||||
|
enabled: true,
|
||||||
|
color: Vec3::new(0.0, 0.5, 1.0),
|
||||||
|
intensity: 1.0,
|
||||||
|
range: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
WorldTransform::from(t),
|
||||||
|
t,
|
||||||
|
cube_mesh.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let t = Transform::new(
|
||||||
|
Vec3::new(0.0, 0.2, -1.5),
|
||||||
|
//Vec3::new(-5.0, 1.0, -0.28),
|
||||||
|
//Vec3::new(-10.0, 0.94, -0.28),
|
||||||
|
Quat::IDENTITY,
|
||||||
|
Vec3::new(0.15, 0.15, 0.15),
|
||||||
|
);
|
||||||
|
|
||||||
|
world.spawn((
|
||||||
|
SpotLight {
|
||||||
|
enabled: true,
|
||||||
|
color: Vec3::new(1.0, 0.0, 0.0),
|
||||||
|
intensity: 1.0,
|
||||||
|
range: 1.5,
|
||||||
|
//cutoff: math::Angle::Degrees(45.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
WorldTransform::from(t),
|
||||||
|
t,
|
||||||
|
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);
|
||||||
|
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||||
|
world.spawn((
|
||||||
|
PointLight {
|
||||||
|
color: Vec3::new(1.0, 1.0, 1.0),
|
||||||
|
|
||||||
|
intensity: 1.0,
|
||||||
|
|
||||||
|
constant: 1.0,
|
||||||
|
linear: 0.045,
|
||||||
|
quadratic: 0.0075,
|
||||||
|
|
||||||
|
ambient: 0.1,
|
||||||
|
diffuse: 1.0,
|
||||||
|
specular: 1.3,
|
||||||
|
},
|
||||||
|
TransformComponent::from(light_tran),
|
||||||
|
ModelComponent(cube_model.clone()),
|
||||||
|
));
|
||||||
|
} */
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
FreeFly3dCamera::default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
//Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue