From 315924f9206d53fe19639aa3742b25e7225cb299 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 2 Nov 2024 19:15:35 -0400 Subject: [PATCH] render: implement 2d sprite rendering --- .vscode/launch.json | 18 + .../src/render/graph/passes/meshes.rs | 8 +- .../lyra-game/src/render/graph/passes/mod.rs | 5 +- .../src/render/graph/passes/shadows.rs | 4 +- .../src/render/graph/passes/sprite.rs | 404 ++++++++++++++++++ .../src/render/graph/passes/transform.rs | 6 +- crates/lyra-game/src/render/render_job.rs | 4 +- crates/lyra-game/src/render/renderer.rs | 6 +- .../src/render/shaders/2d/sprite_main.wgsl | 60 +++ crates/lyra-game/src/render/vertex.rs | 2 +- crates/lyra-game/src/sprite/mod.rs | 2 +- examples/2d/src/main.rs | 29 +- examples/assets/.gitignore | 1 + 13 files changed, 526 insertions(+), 23 deletions(-) create mode 100644 crates/lyra-game/src/render/graph/passes/sprite.rs create mode 100644 crates/lyra-game/src/render/shaders/2d/sprite_main.wgsl create mode 100644 examples/assets/.gitignore diff --git a/.vscode/launch.json b/.vscode/launch.json index fbc7892..2d8bc1c 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,24 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug lyra dim_2d example", + "cargo": { + "args": [ + "build", + "--manifest-path", "${workspaceFolder}/examples/2d/Cargo.toml" + //"--bin=testbed", + ], + "filter": { + "name": "dim_2d", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}/examples/2d" + }, { "type": "lldb", "request": "launch", diff --git a/crates/lyra-game/src/render/graph/passes/meshes.rs b/crates/lyra-game/src/render/graph/passes/meshes.rs index 0873989..c770741 100644 --- a/crates/lyra-game/src/render/graph/passes/meshes.rs +++ b/crates/lyra-game/src/render/graph/passes/meshes.rs @@ -414,12 +414,12 @@ impl Node for MeshPass { view, resolve_target: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { + load: wgpu::LoadOp::Load,/* wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, - }), + }), */ store: wgpu::StoreOp::Store, }, })], @@ -441,9 +441,9 @@ impl Node for MeshPass { for job in render_meshes.iter() { // get the mesh (containing vertices) and the buffers from storage - let buffers = mesh_buffers.get(&job.mesh_uuid); + let buffers = mesh_buffers.get(&job.asset_uuid); if buffers.is_none() { - warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid); + warn!("Skipping job since its mesh is missing {:?}", job.asset_uuid); continue; } let buffers = buffers.unwrap(); diff --git a/crates/lyra-game/src/render/graph/passes/mod.rs b/crates/lyra-game/src/render/graph/passes/mod.rs index 25f8a84..16c3b5e 100644 --- a/crates/lyra-game/src/render/graph/passes/mod.rs +++ b/crates/lyra-game/src/render/graph/passes/mod.rs @@ -29,4 +29,7 @@ mod mesh_prepare; pub use mesh_prepare::*; mod transform; -pub use transform::*; \ No newline at end of file +pub use transform::*; + +mod sprite; +pub use sprite::*; \ No newline at end of file diff --git a/crates/lyra-game/src/render/graph/passes/shadows.rs b/crates/lyra-game/src/render/graph/passes/shadows.rs index ecffcf7..3acc3d4 100644 --- a/crates/lyra-game/src/render/graph/passes/shadows.rs +++ b/crates/lyra-game/src/render/graph/passes/shadows.rs @@ -963,9 +963,9 @@ fn light_shadow_pass_impl<'a>( for job in render_meshes.iter() { // get the mesh (containing vertices) and the buffers from storage - let buffers = mesh_buffers.get(&job.mesh_uuid); + let buffers = mesh_buffers.get(&job.asset_uuid); if buffers.is_none() { - warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid); + warn!("Skipping job since its mesh is missing {:?}", job.asset_uuid); continue; } let buffers = buffers.unwrap(); diff --git a/crates/lyra-game/src/render/graph/passes/sprite.rs b/crates/lyra-game/src/render/graph/passes/sprite.rs new file mode 100644 index 0000000..74b6af8 --- /dev/null +++ b/crates/lyra-game/src/render/graph/passes/sprite.rs @@ -0,0 +1,404 @@ +use std::{ + cell::RefMut, + collections::{HashMap, VecDeque}, + rc::Rc, + sync::Arc, +}; + +use glam::{Vec2, Vec3}; +use image::GenericImageView; +use lyra_ecs::{ + query::{Entities, ResMut}, AtomicRef, Entity, ResourceData +}; +use lyra_game_derive::RenderGraphLabel; +use lyra_resource::{Image, Texture}; +use tracing::{info, instrument, warn}; +use uuid::Uuid; +use wgpu::util::DeviceExt; + +use crate::{ + render::{ + graph::{Node, NodeDesc, NodeType, SlotAttribute}, + render_job::RenderJob, + resource::{ + FragmentState, PipelineDescriptor, RenderPipeline, RenderPipelineDescriptor, Shader, + VertexState, + }, + texture::{res_filter_to_wgpu, res_wrap_to_wgpu}, + transform_buffer_storage::{TransformBuffers, TransformIndex}, + vertex::Vertex2D, + }, + sprite::Sprite, +}; + +use super::{BasePassSlots, RenderAssets}; + +#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] +pub struct SpritePassLabel; + +#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)] +pub enum SpritePassSlots { + SpriteTexture, + SpriteTextureView, + SpriteTextureSampler, +} + +struct SpriteTexture { + texture: wgpu::Texture, + texture_sampler: wgpu::Sampler, + texture_bg: Arc, + + vertex_buffers: wgpu::Buffer, + index_buffers: wgpu::Buffer, +} + +#[derive(Default)] +pub struct SpritePass { + pipeline: Option, + texture_bgl: Option>, + jobs: VecDeque, + + transform_buffers: Option, + sprite_textures: Option, +} + +impl SpritePass { + pub fn new() -> Self { + Self::default() + } + + #[instrument(skip(self, device, sprite))] + fn create_vertex_index_buffers( + &mut self, + device: &wgpu::Device, + sprite: &Sprite, + ) -> (wgpu::Buffer, wgpu::Buffer) { + let tex_dims = sprite + .texture + .data_ref() + .map(|t| t.dimensions()); + if tex_dims.is_none() { + info!("Sprite texture is not loaded, not rendering it until it is!"); + todo!("Wait until texture is loaded"); + } + let tex_dims = tex_dims.unwrap(); + + let vertices = vec![ + // top left + Vertex2D::new(Vec3::new(0.0, 0.0, 0.0), Vec2::new(0.0, 1.0)), + // bottom left + Vertex2D::new(Vec3::new(0.0, tex_dims.1 as f32, 0.0), Vec2::new(0.0, 0.0)), + // top right + Vertex2D::new(Vec3::new(tex_dims.0 as f32, 0.0, 0.0), Vec2::new(1.0, 1.0)), + // bottom right + Vertex2D::new( + Vec3::new(tex_dims.0 as f32, tex_dims.1 as f32, 0.0), + Vec2::new(1.0, 0.0), + ), + ]; + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(vertices.as_slice()), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + }); + + let contents: [u32; 6] = [ + //3, 1, 0, + //0, 2, 3 + 3, 1, 0, // second tri + 0, 2, 3, // first tri + //0, 2, 3, // second tri + ]; + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&contents), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + }); + + (vertex_buffer, index_buffer) + } + + fn load_sprite_texture( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + uuid: &Uuid, + image: &Image, + ) -> Option<(wgpu::Texture, wgpu::Sampler, wgpu::BindGroup)> { + let uuid_str = uuid.to_string(); + let image_dim = image.dimensions(); + let tex = device.create_texture(&wgpu::TextureDescriptor { + label: Some(&format!("sprite_texture_{}", uuid_str)), + size: wgpu::Extent3d { + width: image_dim.0, + height: image_dim.1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, // TODO + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + let tex_view = tex.create_view(&wgpu::TextureViewDescriptor::default()); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + queue.write_texture( + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &tex, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &image.to_rgba8(), + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * image_dim.0), + rows_per_image: Some(image_dim.1), + }, + wgpu::Extent3d { + width: image_dim.0, + height: image_dim.1, + depth_or_array_layers: 1, + }, + ); + + let bgl = self.texture_bgl.as_ref().unwrap(); + let tex_bg = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some(&format!("sprite_texture_bg_{}", uuid_str)), + layout: bgl, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&tex_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + Some((tex, sampler, tex_bg)) + } +} + +impl Node for SpritePass { + fn desc( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + ) -> crate::render::graph::NodeDesc { + let device = &graph.device; + + let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bgl_sprite_main"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, + ], + }); + self.texture_bgl = Some(Arc::new(bgl)); + + let mut desc = NodeDesc::new(NodeType::Render, None, vec![]); + + desc.add_buffer_slot(BasePassSlots::Camera, SlotAttribute::Input, None); + + desc + } + + fn prepare( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + world: &mut lyra_ecs::World, + _: &mut crate::render::graph::RenderGraphContext, + ) { + let device = graph.device(); + let vt = graph.view_target(); + + if self.pipeline.is_none() { + let shader = Rc::new(Shader { + label: Some("sprite_shader".into()), + source: include_str!("../../shaders/2d/sprite_main.wgsl").to_string(), + }); + + let diffuse_bgl = self.texture_bgl.clone().unwrap(); + let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera).clone(); + let transforms = world + .get_resource::() + .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::>(); + let sprite_textures = world + .get_resource_data::>() + .expect("Missing sprite texture store"); + self.sprite_textures = Some(sprite_textures.clone()); + + let transforms = world + .get_resource_data::() + .expect("Missing transform buffers"); + self.transform_buffers = Some(transforms.clone()); + } + + let queue = &graph.queue; + for (entity, sprite, transform_idx, mut sprite_store) in world + .view::<( + Entities, + &Sprite, + &TransformIndex, + ResMut>, + )>() + .iter() + { + if let Some(image) = sprite.texture.data_ref() { + let texture_uuid = sprite.texture.uuid(); + if !sprite_store.contains_key(&texture_uuid) { + // returns `None` if the Texture image is not loaded. + if let Some((tex, samp, tex_bg)) = + self.load_sprite_texture(device, queue, &texture_uuid, &image) + { + let (vertex, index) = self.create_vertex_index_buffers(device, &sprite); + + sprite_store.insert( + texture_uuid, + SpriteTexture { + texture: tex, + texture_sampler: samp, + texture_bg: Arc::new(tex_bg), + vertex_buffers: vertex, + index_buffers: index, + }, + ); + } + } + + self.jobs.push_back(RenderJob { + entity, + shader_id: 0, + asset_uuid: texture_uuid, + transform_id: *transform_idx, + }); + } + } + } + + fn execute( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + _: &crate::render::graph::NodeDesc, + context: &mut crate::render::graph::RenderGraphContext, + ) { + let pipeline = self.pipeline.as_ref().unwrap(); + + let sprite_store = self.sprite_textures.clone().unwrap(); + let sprite_store: AtomicRef> = sprite_store.get(); + let transforms = self.transform_buffers.clone().unwrap(); + let transforms: AtomicRef = transforms.get(); + + let vt = graph.view_target(); + let view = vt.render_view(); + + let camera_bg = graph.bind_group(BasePassSlots::Camera); + + { + let encoder = context.encoder.as_mut().unwrap(); + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("sprite_pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + pass.set_pipeline(pipeline); + + while let Some(job) = self.jobs.pop_front() { + let sprite = sprite_store.get(&job.asset_uuid) + .expect("failed to find SpriteTexture for job asset_uuid"); + + pass.set_bind_group(0, &sprite.texture_bg, &[]); + + // Get the bindgroup for job's transform and bind to it using an offset. + let bindgroup = transforms.bind_group(job.transform_id); + let offset = transforms.buffer_offset(job.transform_id); + pass.set_bind_group(1, bindgroup, &[offset]); + + pass.set_bind_group(2, camera_bg, &[]); + + pass.set_vertex_buffer( + 0, + sprite.vertex_buffers.slice(..), + ); + pass.set_index_buffer(sprite.index_buffers.slice(..), wgpu::IndexFormat::Uint32); + pass.draw_indexed(0..6, 0, 0..1); + } + } + } +} diff --git a/crates/lyra-game/src/render/graph/passes/transform.rs b/crates/lyra-game/src/render/graph/passes/transform.rs index f6bfedd..7ca1fc5 100644 --- a/crates/lyra-game/src/render/graph/passes/transform.rs +++ b/crates/lyra-game/src/render/graph/passes/transform.rs @@ -32,12 +32,12 @@ pub struct InterpTransform { #[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] pub struct TransformsNodeLabel; -#[derive(Debug)] -pub struct TransformsNode {} +#[derive(Debug, Default)] +pub struct TransformsNode; impl TransformsNode { pub fn new() -> Self { - Self {} + Self } } diff --git a/crates/lyra-game/src/render/render_job.rs b/crates/lyra-game/src/render/render_job.rs index e6b0b56..a941094 100755 --- a/crates/lyra-game/src/render/render_job.rs +++ b/crates/lyra-game/src/render/render_job.rs @@ -5,7 +5,7 @@ use super::transform_buffer_storage::TransformIndex; pub struct RenderJob { pub entity: Entity, pub shader_id: u64, - pub mesh_uuid: uuid::Uuid, + pub asset_uuid: uuid::Uuid, pub transform_id: TransformIndex, } @@ -14,7 +14,7 @@ impl RenderJob { Self { entity, shader_id, - mesh_uuid: mesh_buffer_id, + asset_uuid: mesh_buffer_id, transform_id } } diff --git a/crates/lyra-game/src/render/renderer.rs b/crates/lyra-game/src/render/renderer.rs index 317e0b2..880b79a 100755 --- a/crates/lyra-game/src/render/renderer.rs +++ b/crates/lyra-game/src/render/renderer.rs @@ -9,7 +9,7 @@ use lyra_game_derive::RenderGraphLabel; use tracing::{debug, instrument, warn}; use winit::window::Window; -use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, ShadowMapsPass, ShadowMapsPassLabel, SubGraphNode, TransformsNode, TransformsNodeLabel, ViewTarget}; +use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, ShadowMapsPass, ShadowMapsPassLabel, SpritePass, SpritePassLabel, SubGraphNode, TransformsNode, TransformsNodeLabel, ViewTarget}; use super::graph::RenderGraph; use super::{resource::RenderPipeline, render_job::RenderJob}; @@ -163,6 +163,10 @@ impl BasicRenderer { forward_plus_graph.add_node(MeshesPassLabel, MeshPass::new(material_bgl)); forward_plus_graph.add_edge(TransformsNodeLabel, MeshPrepNodeLabel); + debug!("Adding sprite pass"); + forward_plus_graph.add_node(SpritePassLabel, SpritePass::new()); + forward_plus_graph.add_edge(TransformsNodeLabel, SpritePassLabel); + forward_plus_graph.add_edge(LightBasePassLabel, LightCullComputePassLabel); forward_plus_graph.add_edge(LightCullComputePassLabel, MeshesPassLabel); forward_plus_graph.add_edge(MeshPrepNodeLabel, MeshesPassLabel); diff --git a/crates/lyra-game/src/render/shaders/2d/sprite_main.wgsl b/crates/lyra-game/src/render/shaders/2d/sprite_main.wgsl new file mode 100644 index 0000000..d091011 --- /dev/null +++ b/crates/lyra-game/src/render/shaders/2d/sprite_main.wgsl @@ -0,0 +1,60 @@ +const ALPHA_CUTOFF = 0.1; + +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, + @location(1) world_position: vec3, +} + +struct TransformData { + transform: mat4x4, + normal_matrix: mat4x4, +} + +struct CameraUniform { + view: mat4x4, + inverse_projection: mat4x4, + view_projection: mat4x4, + projection: mat4x4, + position: vec3, + tile_debug: u32, +} + +@group(1) @binding(0) +var u_model_transform_data: TransformData; + +@group(2) @binding(0) +var u_camera: CameraUniform; + +@vertex +fn vs_main( + in: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + var world_position: vec4 = u_model_transform_data.transform * vec4(in.position, 1.0); + out.world_position = world_position.xyz; + out.tex_coords = in.tex_coords; + out.clip_position = u_camera.view_projection * world_position; + return out; +} + +@group(0) @binding(0) +var t_diffuse: texture_2d; + +@group(0) @binding(1) +var s_diffuse: sampler; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); + if (object_color.a < ALPHA_CUTOFF) { + discard; + } + + return object_color; +} \ No newline at end of file diff --git a/crates/lyra-game/src/render/vertex.rs b/crates/lyra-game/src/render/vertex.rs index afb81ed..63b26c1 100755 --- a/crates/lyra-game/src/render/vertex.rs +++ b/crates/lyra-game/src/render/vertex.rs @@ -75,7 +75,7 @@ impl Vertex2D { pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, + array_stride: std::mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { diff --git a/crates/lyra-game/src/sprite/mod.rs b/crates/lyra-game/src/sprite/mod.rs index 198af3c..ba71765 100644 --- a/crates/lyra-game/src/sprite/mod.rs +++ b/crates/lyra-game/src/sprite/mod.rs @@ -45,7 +45,7 @@ impl Pivot { #[derive(Clone, Component, Reflect)] pub struct Sprite { - pub texture: ResHandle, + pub texture: ResHandle, pub color: Vec3, pub pivot: Pivot, } diff --git a/examples/2d/src/main.rs b/examples/2d/src/main.rs index 50e023f..918090c 100644 --- a/examples/2d/src/main.rs +++ b/examples/2d/src/main.rs @@ -1,12 +1,10 @@ use lyra_engine::{ - assets::ResourceManager, gltf::Gltf, ecs::query::View, game::App, input::{ + assets::{Image, ResourceManager, Texture}, ecs::query::View, game::App, gltf::Gltf, input::{ Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, }, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{ - CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, - ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, - ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, - }, winit::WindowOptions + 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 + }, sprite::{self, Sprite}, winit::WindowOptions }; #[async_std::main] @@ -85,7 +83,8 @@ async fn main() { .with_plugin(setup_scene_plugin) .with_plugin(action_handler_plugin) //.with_plugin(camera_debug_plugin) - .with_plugin(FreeFlyCameraPlugin); + .with_plugin(FreeFlyCameraPlugin) + .with_system("system_update_world_transforms", system_update_world_transforms, &[]); a.run(); } @@ -113,14 +112,28 @@ fn setup_scene_plugin(app: &mut App) { cube_gltf.wait_recurse_dependencies_load(); let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0]; - drop(resman); + let image = resman.request::("../assets/Egg_item.png") + .unwrap(); + image.wait_recurse_dependencies_load(); + + drop(resman); world.spawn(( cube_mesh.clone(), WorldTransform::default(), Transform::from_xyz(0.0, 0.0, -2.0), )); + world.spawn(( + Sprite { + texture: image, + color: Vec3::ONE, + pivot: sprite::Pivot::Center, + }, + WorldTransform::default(), + Transform::from_xyz(0.0, 0.0, -20.0), + )); + { let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0); light_tran.scale = Vec3::new(0.5, 0.5, 0.5); @@ -136,7 +149,7 @@ fn setup_scene_plugin(app: &mut App) { )); } - let mut camera = CameraComponent::new_2d(); + let mut camera = CameraComponent::new_3d(); camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5); world.spawn((camera, FreeFlyCamera::default())); } diff --git a/examples/assets/.gitignore b/examples/assets/.gitignore new file mode 100644 index 0000000..2e5f37a --- /dev/null +++ b/examples/assets/.gitignore @@ -0,0 +1 @@ +Egg_item.png \ No newline at end of file