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]]
|
||||
name = "bytemuck"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
|
||||
checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
@ -1893,6 +1893,7 @@ dependencies = [
|
|||
name = "lyra-math"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"glam",
|
||||
]
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ pub use render_target::*;
|
|||
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{debug_span, instrument, trace, warn};
|
||||
use wgpu::CommandEncoder;
|
||||
use wgpu::{util::DeviceExt, BufferUsages, CommandEncoder};
|
||||
|
||||
use super::{resource::{ComputePipeline, Pass, Pipeline, RenderPipeline}, Shader};
|
||||
|
||||
|
@ -543,6 +543,15 @@ impl RenderGraph {
|
|||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubGraphNode {
|
||||
|
|
|
@ -20,7 +20,7 @@ use tracing::{debug, warn};
|
|||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::render::{
|
||||
graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue}, light::{directional::DirectionalLight, LightType, PointLight, SpotLight}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, 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};
|
||||
|
@ -98,7 +98,7 @@ impl ShadowMapsPass {
|
|||
}),
|
||||
);
|
||||
|
||||
let atlas = TextureAtlas::new(
|
||||
let atlas = PackedTextureAtlas::new(
|
||||
device,
|
||||
wgpu::TextureFormat::Depth32Float,
|
||||
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
|
@ -1062,14 +1062,14 @@ impl LightShadowMapId {
|
|||
|
||||
/// An ecs resource storing the [`TextureAtlas`] of shadow maps.
|
||||
#[derive(Clone)]
|
||||
pub struct LightShadowMapAtlas(Arc<RwLock<TextureAtlas>>);
|
||||
pub struct LightShadowMapAtlas(Arc<RwLock<PackedTextureAtlas>>);
|
||||
|
||||
impl LightShadowMapAtlas {
|
||||
pub fn get(&self) -> RwLockReadGuard<TextureAtlas> {
|
||||
pub fn get(&self) -> RwLockReadGuard<PackedTextureAtlas> {
|
||||
self.0.read().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_mut(&self) -> RwLockWriteGuard<TextureAtlas> {
|
||||
pub fn get_mut(&self) -> RwLockWriteGuard<PackedTextureAtlas> {
|
||||
self.0.write().unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,39 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
use glam::{Vec2, Vec3};
|
||||
use glam::{UVec2, Vec2, Vec3};
|
||||
use image::GenericImageView;
|
||||
use lyra_ecs::{
|
||||
query::{Entities, ResMut}, AtomicRef, ResourceData
|
||||
query::{filter::Or, Entities, ResMut, TickOf}, AtomicRef, Entity, ResourceData
|
||||
};
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use lyra_math::URect;
|
||||
use lyra_resource::Image;
|
||||
use tracing::{info, instrument, warn};
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{debug, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::{
|
||||
render::{
|
||||
graph::{Node, NodeDesc, NodeType, SlotAttribute},
|
||||
render_job::RenderJob,
|
||||
resource::{
|
||||
FragmentState, RenderPipeline, RenderPipelineDescriptor,
|
||||
VertexState,
|
||||
},
|
||||
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
||||
transform_buffer_storage::{TransformBuffers, TransformIndex},
|
||||
vertex::Vertex2D,
|
||||
},
|
||||
sprite::Sprite,
|
||||
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;
|
||||
|
||||
|
@ -48,7 +52,9 @@ struct SpriteTexture {
|
|||
#[allow(dead_code)]
|
||||
sampler: wgpu::Sampler,
|
||||
texture_bg: Arc<wgpu::BindGroup>,
|
||||
}
|
||||
|
||||
struct SpriteBuffers {
|
||||
vertex_buffers: wgpu::Buffer,
|
||||
index_buffers: wgpu::Buffer,
|
||||
}
|
||||
|
@ -58,9 +64,13 @@ 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>,
|
||||
sprite_textures: Option<ResourceData>,
|
||||
texture_store: Option<ResourceData>,
|
||||
buffer_store: Option<ResourceData>,
|
||||
}
|
||||
|
||||
impl SpritePass {
|
||||
|
@ -68,32 +78,22 @@ impl SpritePass {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
#[instrument(skip(self, device, sprite))]
|
||||
#[instrument(skip(self, device))]
|
||||
fn create_vertex_index_buffers(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
sprite: &Sprite,
|
||||
dimensions: UVec2,
|
||||
) -> (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)),
|
||||
Vertex2D::new(Vec3::new(0.0, dimensions.y 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)),
|
||||
Vertex2D::new(Vec3::new(dimensions.x 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),
|
||||
Vec3::new(dimensions.x as f32, dimensions.y as f32, 0.0),
|
||||
Vec2::new(1.0, 0.0),
|
||||
),
|
||||
];
|
||||
|
@ -109,7 +109,7 @@ impl SpritePass {
|
|||
//0, 2, 3
|
||||
3, 1, 0, // second tri
|
||||
0, 2, 3, // first tri
|
||||
//0, 2, 3, // second tri
|
||||
//0, 2, 3, // second tri
|
||||
];
|
||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
|
@ -175,6 +175,7 @@ impl SpritePass {
|
|||
},
|
||||
);
|
||||
|
||||
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)),
|
||||
|
@ -188,6 +189,14 @@ impl SpritePass {
|
|||
binding: 1,
|
||||
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 {
|
||||
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: &[
|
||||
|
@ -221,9 +241,20 @@ impl Node for SpritePass {
|
|||
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![]);
|
||||
|
||||
|
@ -242,7 +273,11 @@ impl Node for SpritePass {
|
|||
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"))
|
||||
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();
|
||||
|
@ -284,10 +319,14 @@ impl Node for SpritePass {
|
|||
|
||||
drop(transforms);
|
||||
world.add_resource_default_if_absent::<RenderAssets<SpriteTexture>>();
|
||||
let sprite_textures = world
|
||||
.get_resource_data::<RenderAssets<SpriteTexture>>()
|
||||
.expect("Missing sprite texture store");
|
||||
self.sprite_textures = Some(sprite_textures.clone());
|
||||
let 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>()
|
||||
|
@ -295,46 +334,88 @@ impl Node for SpritePass {
|
|||
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, transform_idx, mut sprite_store) in world
|
||||
for (entity, (sprite, atlas_sprite), transform_idx, mut texture_store, mut buffer_store) in world
|
||||
.view::<(
|
||||
Entities,
|
||||
&Sprite,
|
||||
Or<&Sprite, (&AtlasSprite, TickOf<AtlasSprite>)>,
|
||||
&TransformIndex,
|
||||
ResMut<RenderAssets<SpriteTexture>>,
|
||||
ResMut<FxHashMap<Entity, SpriteBuffers>>,
|
||||
)>()
|
||||
.iter()
|
||||
{
|
||||
if let Some(image) = sprite.texture.data_ref() {
|
||||
let texture_uuid = sprite.texture.uuid();
|
||||
if !sprite_store.contains_key(&texture_uuid) {
|
||||
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)
|
||||
{
|
||||
let (vertex, index) = self.create_vertex_index_buffers(device, &sprite);
|
||||
|
||||
sprite_store.insert(
|
||||
texture_store.insert(
|
||||
texture_uuid,
|
||||
SpriteTexture {
|
||||
texture,
|
||||
sampler,
|
||||
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 {
|
||||
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(
|
||||
|
@ -345,8 +426,10 @@ impl Node for SpritePass {
|
|||
) {
|
||||
let pipeline = self.pipeline.as_ref().unwrap();
|
||||
|
||||
let sprite_store = self.sprite_textures.clone().unwrap();
|
||||
let sprite_store: AtomicRef<RenderAssets<SpriteTexture>> = sprite_store.get();
|
||||
let 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();
|
||||
|
||||
|
@ -379,10 +462,11 @@ impl Node for SpritePass {
|
|||
pass.set_pipeline(pipeline);
|
||||
|
||||
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");
|
||||
|
||||
pass.set_bind_group(0, &sprite.texture_bg, &[]);
|
||||
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);
|
||||
|
@ -391,12 +475,16 @@ impl Node for SpritePass {
|
|||
|
||||
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);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@ 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 {
|
||||
|
@ -16,6 +18,11 @@ struct TransformData {
|
|||
normal_matrix: mat4x4<f32>,
|
||||
}
|
||||
|
||||
struct URect {
|
||||
min: vec2<u32>,
|
||||
max: vec2<u32>,
|
||||
}
|
||||
|
||||
struct CameraUniform {
|
||||
view: mat4x4<f32>,
|
||||
inverse_projection: mat4x4<f32>,
|
||||
|
@ -40,6 +47,7 @@ fn vs_main(
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -49,9 +57,26 @@ 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 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) {
|
||||
discard;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ impl AtlasFrame {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TextureAtlas<P: AtlasPacker = SkylinePacker> {
|
||||
pub struct PackedTextureAtlas<P: AtlasPacker = SkylinePacker> {
|
||||
atlas_size: UVec2,
|
||||
|
||||
texture_format: wgpu::TextureFormat,
|
||||
|
@ -38,7 +38,7 @@ pub struct TextureAtlas<P: AtlasPacker = SkylinePacker> {
|
|||
packer: P,
|
||||
}
|
||||
|
||||
impl<P: AtlasPacker> TextureAtlas<P> {
|
||||
impl<P: AtlasPacker> PackedTextureAtlas<P> {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
|
|
|
@ -3,6 +3,9 @@ use lyra_reflect::Reflect;
|
|||
use lyra_resource::ResHandle;
|
||||
use lyra_math::{Vec3, Vec2};
|
||||
|
||||
mod texture_atlas;
|
||||
pub use texture_atlas::*;
|
||||
|
||||
/// How the sprite is positioned and rotated relative to its [`Transform`].
|
||||
///
|
||||
/// 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
|
||||
|
||||
[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;
|
||||
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 use transform::*;
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
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 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),
|
||||
|
@ -20,6 +23,10 @@ impl Rect {
|
|||
max
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dimensions(&self) -> Vec2 {
|
||||
(self.max - self.min).abs()
|
||||
}
|
||||
}
|
||||
|
||||
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::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::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::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!(
|
||||
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.
|
||||
///
|
||||
/// The resource cannot be requested with [`ResourceManager::request`], it can only be
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
use lyra_engine::{
|
||||
assets::{Image, ResourceManager},
|
||||
game::App,
|
||||
gltf::Gltf,
|
||||
input::{
|
||||
assets::{Image, ResourceManager}, ecs::query::{Res, ResMut, View}, game::App, gltf::Gltf, input::{
|
||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||
InputActionPlugin, KeyCode, LayoutId,
|
||||
},
|
||||
math::{self, Transform, Vec3},
|
||||
render::light::directional::DirectionalLight,
|
||||
scene::{
|
||||
}, math::{self, Rect, Transform, URect, UVec2, Vec2, 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
|
||||
},
|
||||
sprite::{self, Sprite},
|
||||
}, sprite::{self, AtlasSprite, Pivot, Sprite, TextureAtlas}, DeltaTime
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
|
@ -100,6 +94,9 @@ async fn main() {
|
|||
}
|
||||
|
||||
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 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();
|
||||
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);
|
||||
world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, 0.0, -2.0),
|
||||
));
|
||||
|
||||
world.spawn((
|
||||
Sprite {
|
||||
texture: image,
|
||||
/* Sprite {
|
||||
texture: sprite,
|
||||
color: Vec3::ONE,
|
||||
pivot: sprite::Pivot::Center,
|
||||
},
|
||||
}, */
|
||||
sprite,
|
||||
WorldTransform::default(),
|
||||
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),
|
||||
TopDown2dCamera {
|
||||
zoom_speed: Some(0.1),
|
||||
zoom_speed: Some(0.2),
|
||||
speed: 14.0,
|
||||
..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