implement texture atlases for sprites, allow storage of assets not from a loader
This commit is contained in:
parent
b8003795fa
commit
4018fdaa80
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ 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}, Shader};
|
use super::{resource::{ComputePipeline, Pass, Pipeline, RenderPipeline}, Shader};
|
||||||
|
|
||||||
|
@ -543,6 +543,15 @@ impl RenderGraph {
|
||||||
shader.wait_for_load()?;
|
shader.wait_for_load()?;
|
||||||
Ok(shader)
|
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SubGraphNode {
|
pub struct SubGraphNode {
|
||||||
|
|
|
@ -20,7 +20,7 @@ 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}, light::{directional::DirectionalLight, LightType, PointLight, SpotLight}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState}, transform_buffer_storage::TransformBuffers, vertex::Vertex, AtlasFrame, GpuSlotBuffer, Shader, TextureAtlas
|
graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue}, light::{directional::DirectionalLight, LightType, PointLight, SpotLight}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState}, transform_buffer_storage::TransformBuffers, vertex::Vertex, AtlasFrame, GpuSlotBuffer, Shader, PackedTextureAtlas
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
|
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
|
||||||
|
@ -98,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,
|
||||||
|
@ -1062,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,39 @@
|
||||||
use std::{
|
use std::{collections::VecDeque, sync::Arc};
|
||||||
collections::VecDeque,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use glam::{Vec2, Vec3};
|
use glam::{UVec2, Vec2, Vec3};
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
use lyra_ecs::{
|
use lyra_ecs::{
|
||||||
query::{Entities, ResMut}, AtomicRef, ResourceData
|
query::{filter::Or, Entities, ResMut, TickOf}, AtomicRef, Entity, ResourceData
|
||||||
};
|
};
|
||||||
use lyra_game_derive::RenderGraphLabel;
|
use lyra_game_derive::RenderGraphLabel;
|
||||||
|
use lyra_math::URect;
|
||||||
use lyra_resource::Image;
|
use lyra_resource::Image;
|
||||||
use tracing::{info, instrument, warn};
|
use rustc_hash::FxHashMap;
|
||||||
|
use tracing::{debug, instrument, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
render::{
|
render::{
|
||||||
graph::{Node, NodeDesc, NodeType, SlotAttribute},
|
graph::{Node, NodeDesc, NodeType, SlotAttribute},
|
||||||
render_job::RenderJob,
|
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
||||||
resource::{
|
|
||||||
FragmentState, RenderPipeline, RenderPipelineDescriptor,
|
|
||||||
VertexState,
|
|
||||||
},
|
|
||||||
transform_buffer_storage::{TransformBuffers, TransformIndex},
|
transform_buffer_storage::{TransformBuffers, TransformIndex},
|
||||||
vertex::Vertex2D,
|
vertex::Vertex2D,
|
||||||
},
|
},
|
||||||
sprite::Sprite,
|
sprite::{AtlasSprite, Sprite},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{BasePassSlots, RenderAssets};
|
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)]
|
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||||
pub struct SpritePassLabel;
|
pub struct SpritePassLabel;
|
||||||
|
|
||||||
|
@ -48,7 +52,9 @@ struct SpriteTexture {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
sampler: wgpu::Sampler,
|
sampler: wgpu::Sampler,
|
||||||
texture_bg: Arc<wgpu::BindGroup>,
|
texture_bg: Arc<wgpu::BindGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpriteBuffers {
|
||||||
vertex_buffers: wgpu::Buffer,
|
vertex_buffers: wgpu::Buffer,
|
||||||
index_buffers: wgpu::Buffer,
|
index_buffers: wgpu::Buffer,
|
||||||
}
|
}
|
||||||
|
@ -58,9 +64,13 @@ pub struct SpritePass {
|
||||||
pipeline: Option<RenderPipeline>,
|
pipeline: Option<RenderPipeline>,
|
||||||
texture_bgl: Option<Arc<wgpu::BindGroupLayout>>,
|
texture_bgl: Option<Arc<wgpu::BindGroupLayout>>,
|
||||||
jobs: VecDeque<RenderJob>,
|
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>,
|
transform_buffers: Option<ResourceData>,
|
||||||
sprite_textures: Option<ResourceData>,
|
texture_store: Option<ResourceData>,
|
||||||
|
buffer_store: Option<ResourceData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpritePass {
|
impl SpritePass {
|
||||||
|
@ -68,32 +78,22 @@ impl SpritePass {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self, device, sprite))]
|
#[instrument(skip(self, device))]
|
||||||
fn create_vertex_index_buffers(
|
fn create_vertex_index_buffers(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
sprite: &Sprite,
|
dimensions: UVec2,
|
||||||
) -> (wgpu::Buffer, wgpu::Buffer) {
|
) -> (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![
|
let vertices = vec![
|
||||||
// top left
|
// top left
|
||||||
Vertex2D::new(Vec3::new(0.0, 0.0, 0.0), Vec2::new(0.0, 1.0)),
|
Vertex2D::new(Vec3::new(0.0, 0.0, 0.0), Vec2::new(0.0, 1.0)),
|
||||||
// bottom left
|
// bottom left
|
||||||
Vertex2D::new(Vec3::new(0.0, tex_dims.1 as f32, 0.0), Vec2::new(0.0, 0.0)),
|
Vertex2D::new(Vec3::new(0.0, dimensions.y as f32, 0.0), Vec2::new(0.0, 0.0)),
|
||||||
// top right
|
// top right
|
||||||
Vertex2D::new(Vec3::new(tex_dims.0 as f32, 0.0, 0.0), Vec2::new(1.0, 1.0)),
|
Vertex2D::new(Vec3::new(dimensions.x as f32, 0.0, 0.0), Vec2::new(1.0, 1.0)),
|
||||||
// bottom right
|
// bottom right
|
||||||
Vertex2D::new(
|
Vertex2D::new(
|
||||||
Vec3::new(tex_dims.0 as f32, tex_dims.1 as f32, 0.0),
|
Vec3::new(dimensions.x as f32, dimensions.y as f32, 0.0),
|
||||||
Vec2::new(1.0, 0.0),
|
Vec2::new(1.0, 0.0),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -175,6 +175,7 @@ impl SpritePass {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let frames = self.atlas_frames_buf.as_ref().unwrap();
|
||||||
let bgl = self.texture_bgl.as_ref().unwrap();
|
let bgl = self.texture_bgl.as_ref().unwrap();
|
||||||
let tex_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let tex_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some(&format!("sprite_texture_bg_{}", uuid_str)),
|
label: Some(&format!("sprite_texture_bg_{}", uuid_str)),
|
||||||
|
@ -188,6 +189,14 @@ impl SpritePass {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
},
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
|
buffer: &frames,
|
||||||
|
offset: 0,
|
||||||
|
size: None,
|
||||||
|
}),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -202,6 +211,17 @@ impl Node for SpritePass {
|
||||||
) -> crate::render::graph::NodeDesc {
|
) -> crate::render::graph::NodeDesc {
|
||||||
let device = &graph.device;
|
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 {
|
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some("bgl_sprite_main"),
|
label: Some("bgl_sprite_main"),
|
||||||
entries: &[
|
entries: &[
|
||||||
|
@ -221,9 +241,20 @@ impl Node for SpritePass {
|
||||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||||
count: None,
|
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.texture_bgl = Some(Arc::new(bgl));
|
||||||
|
self.atlas_frames_buf = Some(Arc::new(atlas_frames));
|
||||||
|
|
||||||
let mut desc = NodeDesc::new(NodeType::Render, None, vec![]);
|
let mut desc = NodeDesc::new(NodeType::Render, None, vec![]);
|
||||||
|
|
||||||
|
@ -242,7 +273,11 @@ impl Node for SpritePass {
|
||||||
let vt = graph.view_target();
|
let vt = graph.view_target();
|
||||||
|
|
||||||
if self.pipeline.is_none() {
|
if self.pipeline.is_none() {
|
||||||
let shader = graph.load_shader_str("sprite_shader", include_str!("../../shaders/2d/sprite_main.wgsl"))
|
let shader = graph
|
||||||
|
.load_shader_str(
|
||||||
|
"sprite_shader",
|
||||||
|
include_str!("../../shaders/2d/sprite_main.wgsl"),
|
||||||
|
)
|
||||||
.expect("failed to load wgsl shader from manager");
|
.expect("failed to load wgsl shader from manager");
|
||||||
|
|
||||||
let diffuse_bgl = self.texture_bgl.clone().unwrap();
|
let diffuse_bgl = self.texture_bgl.clone().unwrap();
|
||||||
|
@ -284,10 +319,14 @@ impl Node for SpritePass {
|
||||||
|
|
||||||
drop(transforms);
|
drop(transforms);
|
||||||
world.add_resource_default_if_absent::<RenderAssets<SpriteTexture>>();
|
world.add_resource_default_if_absent::<RenderAssets<SpriteTexture>>();
|
||||||
let sprite_textures = world
|
let texture_store = world
|
||||||
.get_resource_data::<RenderAssets<SpriteTexture>>()
|
.get_resource_data::<RenderAssets<SpriteTexture>>().unwrap();
|
||||||
.expect("Missing sprite texture store");
|
self.texture_store = Some(texture_store.clone());
|
||||||
self.sprite_textures = Some(sprite_textures.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
|
let transforms = world
|
||||||
.get_resource_data::<TransformBuffers>()
|
.get_resource_data::<TransformBuffers>()
|
||||||
|
@ -295,46 +334,88 @@ impl Node for SpritePass {
|
||||||
self.transform_buffers = Some(transforms.clone());
|
self.transform_buffers = Some(transforms.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut v = Vec::with_capacity(500);
|
||||||
|
|
||||||
|
let world_tick = world.current_tick();
|
||||||
let queue = &graph.queue;
|
let queue = &graph.queue;
|
||||||
for (entity, sprite, transform_idx, mut sprite_store) in world
|
for (entity, (sprite, atlas_sprite), transform_idx, mut texture_store, mut buffer_store) in world
|
||||||
.view::<(
|
.view::<(
|
||||||
Entities,
|
Entities,
|
||||||
&Sprite,
|
Or<&Sprite, (&AtlasSprite, TickOf<AtlasSprite>)>,
|
||||||
&TransformIndex,
|
&TransformIndex,
|
||||||
ResMut<RenderAssets<SpriteTexture>>,
|
ResMut<RenderAssets<SpriteTexture>>,
|
||||||
|
ResMut<FxHashMap<Entity, SpriteBuffers>>,
|
||||||
)>()
|
)>()
|
||||||
.iter()
|
.iter()
|
||||||
{
|
{
|
||||||
if let Some(image) = sprite.texture.data_ref() {
|
let tex = if let Some(sprite) = &sprite {
|
||||||
let texture_uuid = sprite.texture.uuid();
|
sprite.texture.clone()//.data_ref()
|
||||||
if !sprite_store.contains_key(&texture_uuid) {
|
} 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.
|
// returns `None` if the Texture image is not loaded.
|
||||||
if let Some((texture, sampler, tex_bg)) =
|
if let Some((texture, sampler, tex_bg)) =
|
||||||
self.load_sprite_texture(device, queue, &texture_uuid, &image)
|
self.load_sprite_texture(device, queue, &texture_uuid, &image)
|
||||||
{
|
{
|
||||||
let (vertex, index) = self.create_vertex_index_buffers(device, &sprite);
|
texture_store.insert(
|
||||||
|
|
||||||
sprite_store.insert(
|
|
||||||
texture_uuid,
|
texture_uuid,
|
||||||
SpriteTexture {
|
SpriteTexture {
|
||||||
texture,
|
texture,
|
||||||
sampler,
|
sampler,
|
||||||
texture_bg: Arc::new(tex_bg),
|
texture_bg: Arc::new(tex_bg),
|
||||||
vertex_buffers: vertex,
|
|
||||||
index_buffers: index,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
self.jobs.push_back(RenderJob {
|
||||||
entity,
|
entity,
|
||||||
shader_id: 0,
|
shader_id: 0,
|
||||||
asset_uuid: texture_uuid,
|
asset_uuid: texture_uuid,
|
||||||
transform_id: *transform_idx,
|
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(
|
fn execute(
|
||||||
|
@ -345,8 +426,10 @@ impl Node for SpritePass {
|
||||||
) {
|
) {
|
||||||
let pipeline = self.pipeline.as_ref().unwrap();
|
let pipeline = self.pipeline.as_ref().unwrap();
|
||||||
|
|
||||||
let sprite_store = self.sprite_textures.clone().unwrap();
|
let texture_store = self.texture_store.clone().unwrap();
|
||||||
let sprite_store: AtomicRef<RenderAssets<SpriteTexture>> = sprite_store.get();
|
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 = self.transform_buffers.clone().unwrap();
|
||||||
let transforms: AtomicRef<TransformBuffers> = transforms.get();
|
let transforms: AtomicRef<TransformBuffers> = transforms.get();
|
||||||
|
|
||||||
|
@ -379,10 +462,11 @@ impl Node for SpritePass {
|
||||||
pass.set_pipeline(pipeline);
|
pass.set_pipeline(pipeline);
|
||||||
|
|
||||||
while let Some(job) = self.jobs.pop_front() {
|
while let Some(job) = self.jobs.pop_front() {
|
||||||
let sprite = sprite_store.get(&job.asset_uuid)
|
// bind texture
|
||||||
|
let tex = texture_store
|
||||||
|
.get(&job.asset_uuid)
|
||||||
.expect("failed to find SpriteTexture for job asset_uuid");
|
.expect("failed to find SpriteTexture for job asset_uuid");
|
||||||
|
pass.set_bind_group(0, &tex.texture_bg, &[]);
|
||||||
pass.set_bind_group(0, &sprite.texture_bg, &[]);
|
|
||||||
|
|
||||||
// Get the bindgroup for job's transform and bind to it using an offset.
|
// Get the bindgroup for job's transform and bind to it using an offset.
|
||||||
let bindgroup = transforms.bind_group(job.transform_id);
|
let bindgroup = transforms.bind_group(job.transform_id);
|
||||||
|
@ -391,12 +475,16 @@ impl Node for SpritePass {
|
||||||
|
|
||||||
pass.set_bind_group(2, camera_bg, &[]);
|
pass.set_bind_group(2, camera_bg, &[]);
|
||||||
|
|
||||||
pass.set_vertex_buffer(
|
// set vertex and index buffers
|
||||||
0,
|
let bufs = buffer_store
|
||||||
sprite.vertex_buffers.slice(..),
|
.get(&job.entity)
|
||||||
);
|
.expect("failed to find buffers for job entity");
|
||||||
pass.set_index_buffer(sprite.index_buffers.slice(..), wgpu::IndexFormat::Uint32);
|
pass.set_vertex_buffer(0, bufs.vertex_buffers.slice(..));
|
||||||
pass.draw_indexed(0..6, 0, 0..1);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@ const ALPHA_CUTOFF = 0.1;
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
@location(1) tex_coords: vec2<f32>,
|
@location(1) tex_coords: vec2<f32>,
|
||||||
|
@builtin(instance_index) instance_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
@location(0) tex_coords: vec2<f32>,
|
@location(0) tex_coords: vec2<f32>,
|
||||||
@location(1) world_position: vec3<f32>,
|
@location(1) world_position: vec3<f32>,
|
||||||
|
@location(2) instance_index: u32
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TransformData {
|
struct TransformData {
|
||||||
|
@ -16,6 +18,11 @@ struct TransformData {
|
||||||
normal_matrix: mat4x4<f32>,
|
normal_matrix: mat4x4<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct URect {
|
||||||
|
min: vec2<u32>,
|
||||||
|
max: vec2<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
struct CameraUniform {
|
struct CameraUniform {
|
||||||
view: mat4x4<f32>,
|
view: mat4x4<f32>,
|
||||||
inverse_projection: mat4x4<f32>,
|
inverse_projection: mat4x4<f32>,
|
||||||
|
@ -40,6 +47,7 @@ fn vs_main(
|
||||||
out.world_position = world_position.xyz;
|
out.world_position = world_position.xyz;
|
||||||
out.tex_coords = in.tex_coords;
|
out.tex_coords = in.tex_coords;
|
||||||
out.clip_position = u_camera.view_projection * world_position;
|
out.clip_position = u_camera.view_projection * world_position;
|
||||||
|
out.instance_index = in.instance_index;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,9 +57,26 @@ var t_diffuse: texture_2d<f32>;
|
||||||
@group(0) @binding(1)
|
@group(0) @binding(1)
|
||||||
var s_diffuse: sampler;
|
var s_diffuse: sampler;
|
||||||
|
|
||||||
|
@group(0) @binding(2)
|
||||||
|
var<storage, read> u_sprite_atlas_frames: array<URect>;
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
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) {
|
if (object_color.a < ALPHA_CUTOFF) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -3,6 +3,9 @@ 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::*;
|
||||||
|
|
||||||
/// 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.
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
use glam::{UVec2, Vec3};
|
||||||
|
use lyra_ecs::Component;
|
||||||
|
use lyra_math::URect;
|
||||||
|
use lyra_reflect::Reflect;
|
||||||
|
use lyra_resource::ResHandle;
|
||||||
|
|
||||||
|
use super::Pivot;
|
||||||
|
|
||||||
|
/// A texture atlas of multiple sprites.
|
||||||
|
#[derive(Clone, Component, Reflect)]
|
||||||
|
pub struct TextureAtlas {
|
||||||
|
pub texture: ResHandle<lyra_resource::Image>,
|
||||||
|
/// The coordinates in the texture where the grid starts.
|
||||||
|
pub grid_offset: UVec2,
|
||||||
|
/// The size of the grid in cells.
|
||||||
|
pub grid_size: UVec2,
|
||||||
|
/// The size of each cell.
|
||||||
|
pub cell_size: UVec2,
|
||||||
|
|
||||||
|
pub sprite_color: Vec3,
|
||||||
|
pub pivot: Pivot,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureAtlas {
|
||||||
|
/// The cell x and y in the grid of a specific index.
|
||||||
|
pub fn index_cell(&self, i: u32) -> UVec2 {
|
||||||
|
let x = i % self.grid_size.x;
|
||||||
|
let y = i / self.grid_size.x;
|
||||||
|
UVec2 { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The coords of the cell at x and y in the grid.
|
||||||
|
///
|
||||||
|
/// The indices are different then the image coords, this is the position in the grid.
|
||||||
|
/// So if you have a 9x7 grid, and wanted to get the 1nd cell on the 2nd row, you'd
|
||||||
|
/// use the values `x = 0, y = 1` (indices start at zero like arrays).
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn cell_coords(&self, x: u32, y: u32) -> UVec2 {
|
||||||
|
UVec2 {
|
||||||
|
x: x * self.cell_size.x,
|
||||||
|
y: y * self.cell_size.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The coords of the cell at an index.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn index_coords(&self, i: u32) -> UVec2 {
|
||||||
|
let cell = self.index_cell(i);
|
||||||
|
|
||||||
|
self.cell_coords(cell.x, cell.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The rectangle of the cell at the x and y indices in the grid.
|
||||||
|
///
|
||||||
|
/// The indices are different then the image coords, this is the position in the grid.
|
||||||
|
/// So if you have a 9x7 grid, and wanted to get the 1nd cell on the 2nd row, you'd
|
||||||
|
/// use the values `x = 0, y = 1` (indices start at zero like arrays).
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn cell_rect(&self, x: u32, y: u32) -> URect {
|
||||||
|
let start = self.cell_coords(x, y);
|
||||||
|
let end = start + self.cell_size;
|
||||||
|
URect { min: start, max: end }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The rectangle of the cell at an index.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn index_rect(&self, i: u32) -> URect {
|
||||||
|
let cell = self.index_cell(i);
|
||||||
|
self.cell_rect(cell.x, cell.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.index_rect(i);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
atlas: atlas.clone(),
|
||||||
|
sprite: rect,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"] }
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,14 @@ pub use area::*;
|
||||||
mod rect;
|
mod rect;
|
||||||
pub use 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::*;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
pub min: Vec2,
|
pub min: Vec2,
|
||||||
pub max: Vec2,
|
pub max: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rect {
|
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 {
|
pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
min: Vec2::new(x1, y1),
|
min: Vec2::new(x1, y1),
|
||||||
|
@ -20,6 +23,10 @@ impl Rect {
|
||||||
max
|
max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dimensions(&self) -> Vec2 {
|
||||||
|
(self.max - self.min).abs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Add for Rect {
|
impl std::ops::Add for Rect {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -184,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
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
use lyra_engine::{
|
use lyra_engine::{
|
||||||
assets::{Image, ResourceManager},
|
assets::{Image, ResourceManager}, ecs::query::{Res, ResMut, View}, game::App, gltf::Gltf, input::{
|
||||||
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, Rect, Transform, URect, UVec2, Vec2, Vec3}, render::light::directional::DirectionalLight, scene::{
|
||||||
math::{self, Transform, Vec3},
|
|
||||||
render::light::directional::DirectionalLight,
|
|
||||||
scene::{
|
|
||||||
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
|
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, AtlasSprite, Pivot, Sprite, TextureAtlas}, DeltaTime
|
||||||
sprite::{self, Sprite},
|
|
||||||
};
|
};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -100,6 +94,9 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
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, &[]);
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
@ -127,19 +124,28 @@ fn setup_scene_plugin(app: &mut App) {
|
||||||
let image = resman.request::<Image>("../assets/Egg_item.png").unwrap();
|
let image = resman.request::<Image>("../assets/Egg_item.png").unwrap();
|
||||||
image.wait_recurse_dependencies_load().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 {
|
||||||
|
texture: soldier,
|
||||||
|
grid_offset: UVec2::ZERO,
|
||||||
|
grid_size: UVec2::new(9, 7),
|
||||||
|
cell_size: UVec2::new(100, 100),
|
||||||
|
sprite_color: Vec3::ONE,
|
||||||
|
pivot: Pivot::default(),
|
||||||
|
});
|
||||||
|
let sprite = AtlasSprite::from_atlas_index(atlas, 9);
|
||||||
|
|
||||||
drop(resman);
|
drop(resman);
|
||||||
world.spawn((
|
|
||||||
cube_mesh.clone(),
|
|
||||||
WorldTransform::default(),
|
|
||||||
Transform::from_xyz(0.0, 0.0, -2.0),
|
|
||||||
));
|
|
||||||
|
|
||||||
world.spawn((
|
world.spawn((
|
||||||
Sprite {
|
/* Sprite {
|
||||||
texture: image,
|
texture: sprite,
|
||||||
color: Vec3::ONE,
|
color: Vec3::ONE,
|
||||||
pivot: sprite::Pivot::Center,
|
pivot: sprite::Pivot::Center,
|
||||||
},
|
}, */
|
||||||
|
sprite,
|
||||||
WorldTransform::default(),
|
WorldTransform::default(),
|
||||||
Transform::from_xyz(0.0, 0.0, -10.0),
|
Transform::from_xyz(0.0, 0.0, -10.0),
|
||||||
));
|
));
|
||||||
|
@ -169,8 +175,41 @@ fn setup_scene_plugin(app: &mut App) {
|
||||||
},
|
},
|
||||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||||
TopDown2dCamera {
|
TopDown2dCamera {
|
||||||
zoom_speed: Some(0.1),
|
zoom_speed: Some(0.2),
|
||||||
|
speed: 14.0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct Timer(f32);
|
||||||
|
|
||||||
|
fn sprite_change(mut timer: ResMut<Timer>, dt: Res<DeltaTime>, view: View<&mut AtlasSprite>) -> anyhow::Result<()> {
|
||||||
|
timer.0 += **dt;
|
||||||
|
|
||||||
|
const TIME: f32 = 0.1;
|
||||||
|
if timer.0 >= TIME {
|
||||||
|
//println!("{t} seconds timer triggered, moving sprite");
|
||||||
|
timer.0 = 0.0;
|
||||||
|
|
||||||
|
for mut a in view.iter() {
|
||||||
|
//println!("a.sprite: {:?}", a.sprite);
|
||||||
|
|
||||||
|
if a.sprite.max.x >= 800 {
|
||||||
|
a.sprite = URect {
|
||||||
|
min: UVec2::new(0, 100),
|
||||||
|
max: UVec2::new(100, 200),
|
||||||
|
};
|
||||||
|
//println!("restart!");
|
||||||
|
} else {
|
||||||
|
a.sprite += URect {
|
||||||
|
min: UVec2::new(100, 0),
|
||||||
|
max: UVec2::new(100, 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!source.txt
|
|
@ -0,0 +1 @@
|
||||||
|
https://zerie.itch.io/tiny-rpg-character-asset-pack
|
Loading…
Reference in New Issue