From 25aa902e0247bda0ce67e55bd1df6753eb270dac Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 17 Apr 2024 20:46:46 -0400 Subject: [PATCH] render: use WorldTransforms in the renderer --- Cargo.lock | 5 +- examples/testbed/src/main.rs | 61 ++++++++++++++---------- lyra-ecs/src/query/filter/or.rs | 64 ++++++++++++++++++++++--- lyra-game/src/render/renderer.rs | 79 ++++++++++++++++--------------- lyra-game/src/scene/mod.rs | 4 +- lyra-resource/src/gltf/loader.rs | 14 ++++-- lyra-scene/src/world_transform.rs | 9 +++- 7 files changed, 161 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ca50aa..eaf2077 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "arrayref" @@ -1865,6 +1865,7 @@ dependencies = [ name = "lyra-scene" version = "0.1.0" dependencies = [ + "anyhow", "lyra-ecs", "lyra-math", ] diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index a43d115..22fe0d5 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -1,6 +1,6 @@ use std::{ptr::NonNull, thread, time::Duration}; -use lyra_engine::{assets::gltf::Gltf, change_tracker::Ct, ecs::{query::{Res, View}, system::{Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::{light::{directional::DirectionalLight, PointLight, SpotLight}, window::{CursorGrabMode, WindowOptions}}, 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::gltf::Gltf, change_tracker::Ct, ecs::{query::{Res, View}, system::{Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::{light::{directional::DirectionalLight, PointLight, SpotLight}, window::{CursorGrabMode, WindowOptions}}, 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}, DeltaTime}; use lyra_engine::assets::ResourceManager; struct FixedTimestep { @@ -97,6 +97,7 @@ async fn main() { world.spawn(( sponza_scene.clone(), + WorldTransform::default(), Transform::from_xyz(0.0, 0.0, 0.0), )); @@ -117,6 +118,15 @@ async fn main() { } { + 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, @@ -125,17 +135,20 @@ async fn main() { range: 2.0, ..Default::default() }, - 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), - ), + 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, @@ -144,17 +157,20 @@ async fn main() { range: 1.0, ..Default::default() }, - 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), - ), + 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, @@ -164,14 +180,8 @@ async fn main() { //cutoff: math::Angle::Degrees(45.0), ..Default::default() }, - 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), - ), + WorldTransform::from(t), + t, cube_mesh.clone(), )); } @@ -245,6 +255,7 @@ async fn main() { }; game.with_system("camera_debug_trigger", sys, &[]); + game.with_system("update_world_transforms", scene::system_update_world_transforms, &[]); }; let action_handler_plugin = |game: &mut Game| { diff --git a/lyra-ecs/src/query/filter/or.rs b/lyra-ecs/src/query/filter/or.rs index 45a376f..fd1e8f2 100644 --- a/lyra-ecs/src/query/filter/or.rs +++ b/lyra-ecs/src/query/filter/or.rs @@ -1,4 +1,36 @@ -use crate::{query::{AsQuery, Query}, Archetype, World}; +use crate::{query::{AsQuery, Fetch, Query}, Archetype, World}; + +pub struct OrFetch<'a, Q1: Query, Q2: Query> { + left: Option>, + right: Option>, +} + +impl<'a, Q1: Query, Q2: Query> Fetch<'a> for OrFetch<'a, Q1, Q2> { + type Item = (Option>, Option>); + + fn dangling() -> Self { + Self { + left: None, + right: None, + } + } + + unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item { + let mut res = (None, None); + + if let Some(left) = self.left.as_mut() { + let i = left.get_item(entity); + res.0 = Some(i); + } + + if let Some(right) = self.right.as_mut() { + let i = right.get_item(entity); + res.1 = Some(i); + } + + res + } +} /// A filter query returning when either `Q1` or `Q2` returns. /// @@ -25,25 +57,34 @@ use crate::{query::{AsQuery, Query}, Archetype, World}; pub struct Or { left: Q1::Query, right: Q2::Query, + can_visit_left: bool, + can_visit_right: bool, } impl Copy for Or {} impl Clone for Or { fn clone(&self) -> Self { - Self { left: self.left.clone(), right: self.right.clone() } + Self { + left: self.left.clone(), + right: self.right.clone(), + can_visit_left: self.can_visit_left, + can_visit_right: self.can_visit_right, + } } } impl Query for Or { - type Item<'a> = (); + type Item<'a> = (Option<::Item<'a>>, Option<::Item<'a>>); - type Fetch<'a> = (); + type Fetch<'a> = OrFetch<'a, Q1::Query, Q2::Query>; fn new() -> Self { Or { left: Q1::Query::new(), right: Q2::Query::new(), + can_visit_left: false, + can_visit_right: false, } } @@ -51,8 +92,19 @@ impl Query for Or { self.left.can_visit_archetype(archetype) || self.right.can_visit_archetype(archetype) } - unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> { - () + unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a Archetype, tick: crate::Tick) -> Self::Fetch<'a> { + let mut f = OrFetch::::dangling(); + + // TODO: store the result of Self::can_visit_archetype so this isn't ran twice + if self.left.can_visit_archetype(archetype) { + f.left = Some(self.left.fetch(world, archetype, tick)); + } + + if self.right.can_visit_archetype(archetype) { + f.right = Some(self.right.fetch(world, archetype, tick)); + } + + f } } diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 9369873..9506e7e 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -6,11 +6,12 @@ use std::borrow::Cow; use glam::Vec3; use instant::Instant; use itertools::izip; -use lyra_ecs::query::filter::{Has, Or}; +use lyra_ecs::query::filter::{Has, Not, Or}; +use lyra_ecs::relation::{ChildOf, RelationOriginComponent}; use lyra_ecs::{Entity, Tick}; use lyra_ecs::query::{Entities, TickOf}; use lyra_ecs::World; -use lyra_scene::SceneGraph; +use lyra_scene::{SceneGraph, WorldTransform}; use tracing::{debug, warn}; use uuid::Uuid; use wgpu::{BindGroupLayout, Limits}; @@ -430,17 +431,16 @@ impl BasicRenderer { impl Renderer for BasicRenderer { fn prepare(&mut self, main_world: &mut World) { let last_epoch = main_world.current_tick(); - + let now_inst = Instant::now(); let mut alive_entities = HashSet::new(); - let now_inst = Instant::now(); - - let view = main_world.filtered_view_iter::<(Entities, &Transform, TickOf), Or, Has>>(); - for (entity, transform, transform_epoch) in view { + let view = main_world.view_iter::<(Entities, &Transform, TickOf, + Or<(&MeshHandle, TickOf), (&SceneHandle, TickOf)>)>(); + + for (entity, transform, transform_epoch, (mesh_pair, scene_pair)) in view { alive_entities.insert(entity); - let mesh_view = main_world.view_one::<(&MeshHandle, TickOf)>(entity); - if let Some((mesh_han, mesh_epoch)) = mesh_view.get() { + if let Some((mesh_han, mesh_epoch)) = mesh_pair { let interop_pos = self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch); if let Some(mesh) = mesh_han.data_ref() { @@ -464,39 +464,37 @@ impl Renderer for BasicRenderer { } } - let scene_view = main_world.view_one::<(&SceneHandle, TickOf)>(entity); - if let Some((scene_han, scene_epoch)) = scene_view.get() { - // TODO: Rendering scenes - + if let Some((scene_han, scene_epoch)) = scene_pair { if let Some(scene) = scene_han.data_ref() { + let view = scene.world().view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); + lyra_scene::system_update_world_transforms(scene.world(), view).unwrap(); + let interpo_pos = self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch); - scene.traverse_down(|sw: &World, mesh_han, pos| { - if let Some(mesh_han) = sw.view_one::<&MeshHandle>(mesh_han.entity()).get() { - if let Some(mesh) = mesh_han.data_ref() { - let mesh_interpo = interpo_pos + pos; - - // if process mesh did not just create a new mesh, and the epoch - // shows that the scene has changed, verify that the mesh buffers - // dont need to be resent to the gpu. - if !self.process_mesh(entity, mesh_interpo, &*mesh, mesh_han.uuid()) - && scene_epoch == last_epoch { - self.check_mesh_buffers(entity, &mesh_han); - } + for (mesh_han, pos) in scene.world().view_iter::<(&MeshHandle, &WorldTransform)>() { + if let Some(mesh) = mesh_han.data_ref() { + let mesh_interpo = interpo_pos + **pos; - let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid()); - let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into()); - let transform_id = self.transform_buffers.update_or_push(&self.queue, &self.render_limits, - group, || ( mesh_interpo.calculate_mat4(), glam::Mat3::from_quat(mesh_interpo.rotation) )); - - let material = mesh.material.as_ref().unwrap() - .data_ref().unwrap(); - let shader = material.shader_uuid.unwrap_or(0); - let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); - self.render_jobs.push_back(job); + // if process mesh did not just create a new mesh, and the epoch + // shows that the scene has changed, verify that the mesh buffers + // dont need to be resent to the gpu. + if !self.process_mesh(entity, mesh_interpo, &*mesh, mesh_han.uuid()) + && scene_epoch == last_epoch { + self.check_mesh_buffers(entity, &mesh_han); } + + let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid()); + let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into()); + let transform_id = self.transform_buffers.update_or_push(&self.queue, &self.render_limits, + group, || ( mesh_interpo.calculate_mat4(), glam::Mat3::from_quat(mesh_interpo.rotation) )); + + let material = mesh.material.as_ref().unwrap() + .data_ref().unwrap(); + let shader = material.shader_uuid.unwrap_or(0); + let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); + self.render_jobs.push_back(job); } - }); + } } } } @@ -567,7 +565,14 @@ impl Renderer for BasicRenderer { render_pass.set_pipeline(pipeline.get_wgpu_pipeline()); // get the mesh (containing vertices) and the buffers from storage - let buffers = self.mesh_buffers.get(&job.mesh_uuid).unwrap(); + let buffers = self.mesh_buffers.get(&job.mesh_uuid); + if buffers.is_none() { + warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid); + continue; + } + let buffers = buffers.unwrap(); + /* let buffers = self.mesh_buffers.get(&job.mesh_uuid) + .expect("missing render job mesh"); */ // Bind the optional texture if let Some(tex) = buffers.material.as_ref() diff --git a/lyra-game/src/scene/mod.rs b/lyra-game/src/scene/mod.rs index 090ad66..3437fba 100644 --- a/lyra-game/src/scene/mod.rs +++ b/lyra-game/src/scene/mod.rs @@ -5,4 +5,6 @@ pub mod camera; pub use camera::*; pub mod free_fly_camera; -pub use free_fly_camera::*; \ No newline at end of file +pub use free_fly_camera::*; + +pub use lyra_scene::*; \ No newline at end of file diff --git a/lyra-resource/src/gltf/loader.rs b/lyra-resource/src/gltf/loader.rs index 37dd5fd..62172dc 100644 --- a/lyra-resource/src/gltf/loader.rs +++ b/lyra-resource/src/gltf/loader.rs @@ -2,8 +2,9 @@ use std::{ffi::OsStr, path::{Path, PathBuf}, sync::Arc}; use glam::{Quat, Vec3}; use instant::Instant; +use lyra_ecs::query; use lyra_math::Transform; -use lyra_scene::{SceneGraph, SceneNode}; +use lyra_scene::{SceneGraph, SceneNode, WorldTransform}; use thiserror::Error; use crate::{loader::{LoaderError, PinedBoxLoaderFuture, ResourceLoader}, util, ResHandle, ResourceData, ResourceManager, ResourceStorage}; @@ -76,7 +77,7 @@ impl ModelLoader { }; node.name = gnode.name().map(str::to_string); - let scene_node = scene.add_node_under(scene_parent, node.transform, ()); + let scene_node = scene.add_node_under(scene_parent, (WorldTransform::from(node.transform), node.transform)); if let Some(mesh) = gnode.mesh() { let mut new_mesh = Mesh::default(); @@ -225,6 +226,10 @@ impl ResourceLoader for ModelLoader { } } + for en in graph.world().view_iter::() { + graph.world().view_one::<(&WorldTransform, &Transform)>(en).get().expect("Scene node is missing world and local transform bundle!"); + } + let graph = ResHandle::new_ready(Some(path.as_str()), graph); gltf_out.scenes.push(graph); @@ -268,6 +273,7 @@ impl ResourceLoader for ModelLoader { #[cfg(test)] mod tests { use lyra_ecs::{query::Entities, relation::ChildOf}; + use lyra_scene::WorldTransform; use crate::tests::busy_wait_resource; @@ -293,7 +299,9 @@ mod tests { .data_ref().unwrap(); let mut node = None; - scene.traverse_down(|_, no, _tran| { + //scene.world().view::(|_, no, tran| { + tran.get().expect("scene node is missing a WorldTransform"); node = Some(no.clone()); }); diff --git a/lyra-scene/src/world_transform.rs b/lyra-scene/src/world_transform.rs index 143dd2a..bd964f8 100644 --- a/lyra-scene/src/world_transform.rs +++ b/lyra-scene/src/world_transform.rs @@ -22,6 +22,12 @@ impl Deref for WorldTransform { } } +impl From for WorldTransform { + fn from(value: Transform) -> Self { + Self(value) + } +} + /// A system that updates the [`WorldTransform`]'s for entities and their children. /// /// For entities without parents, this will update world transform to match local transform. @@ -47,7 +53,8 @@ fn recurse_update_trans(world: &World, parent_transform: &WorldTransform, entity .relates_to::(entity) .into_iter() { - world_tran.0 = parent_transform.0 + *tran; + world_tran.0 = *tran; + world_tran.0.translation = parent_transform.0.translation + tran.translation; next_entities.push((en, world_tran.0.clone())); }