diff --git a/.gitignore b/.gitignore index 4101a23..46ead6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target *.log *.bkp +*.perf +*.data +*.html diff --git a/Cargo.lock b/Cargo.lock index 8c6b8a7..5b36f94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,6 +147,7 @@ dependencies = [ "cgmath", "chrono", "fern", + "fontdue", "image", "log", "pollster", @@ -504,6 +505,16 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontdue" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253" +dependencies = [ + "hashbrown 0.11.2", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -578,7 +589,7 @@ checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" dependencies = [ "bitflags", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -590,6 +601,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -666,7 +686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -1319,6 +1339,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + [[package]] name = "unicode-ident" version = "1.0.5" diff --git a/Cargo.toml b/Cargo.toml index 9b66401..566f042 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ fern = { version = "^0.6.1", features = ["colored"] } bitflags = "^1.3.2" cgmath = { version = "^0.18.0", features = ["bytemuck"] } pollster = "^0.2.5" +fontdue = "0.7.2" # surface creation winit = "^0.26.1" @@ -33,3 +34,6 @@ features = ["png", "jpeg"] [patch.crates-io] cgmath = { path = "cgmath" } +[profile.release] +debug = 1 + diff --git a/assets/DejaVuSansMono.ttf b/assets/DejaVuSansMono.ttf new file mode 100644 index 0000000..f578602 Binary files /dev/null and b/assets/DejaVuSansMono.ttf differ diff --git a/src/canvas.rs b/src/canvas.rs index 83dd4cc..38d47ae 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -28,7 +28,7 @@ pub trait Application { //--Canvas struct----------------------------------------------------------------------------------- pub struct Canvas { default_texture: TextureHandle, - clear_color: Pixel, + clear_color: Color, renderer: WgpuRenderer, } @@ -80,8 +80,14 @@ impl Canvas { ) } - pub fn create_text_sprite(&mut self, _size: Size, _scale: f32) -> TextSprite { - unimplemented!(); + pub fn create_text_sprite(&mut self, text: &'static str, size: Size, text_size: f32) + -> TextSprite { + TextSprite::new( + text, + size, + text_size, + &self.renderer, + ) } pub fn create_shape_sprite(&mut self) -> ShapeSprite { @@ -143,7 +149,7 @@ impl Canvas { sprite.render(&mut self.renderer); } - pub fn set_clear_color(&mut self, color: Pixel) { + pub fn set_clear_color(&mut self, color: Color) { self.clear_color = color; } diff --git a/src/lib.rs b/src/lib.rs index 65ba04c..9902dc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,7 +106,7 @@ mod tests { message )) }) - .level(log::LevelFilter::Trace) + .level(log::LevelFilter::Debug) .chain(std::io::stdout()) .chain(fern::log_file("output.log")?) .apply()?; diff --git a/src/main.rs b/src/main.rs index d842145..c2f1420 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use canvas::{ Application, Canvas, texture::TextureHandle, - sprite::TextureSprite, + sprite::{TextureSprite, TextSprite}, utils::Position, }; @@ -40,10 +40,13 @@ struct ExampleState { pub texture: TextureHandle, pub tex_sprite: TextureSprite, pub sub_sprite: TextureSprite, + pub sub_sprite2: TextureSprite, + pub txt_sprite: TextSprite, pub last_instant: Instant, pub last_offset: u32, pub last_pos: Position, pub last_rot: f32, + pub frame_counter: u8, } struct ExampleApp {} @@ -51,16 +54,35 @@ struct ExampleApp {} impl Application for ExampleApp { fn init(canvas: &mut Canvas) -> Result { - use canvas::utils::Size; + use canvas::{ + utils::{Size, Color}, + sprite::{Sprite, Center}, + }; //// 20 x 20 sprite of a picture let texture = canvas.create_texture_from_file("assets/camel.jpg", None, None, None) .unwrap(); let mut tex_sprite = canvas.create_texture_sprite(Size {w: 1280, h: 720}); tex_sprite.set_texture(texture.clone(), Some(Position {x: 0, y: 0}), 1.0); + tex_sprite.set_center(Center::BotLeft); + tex_sprite.set_position(Position {x: 0, y: 0}); - let mut sub_sprite = canvas.create_texture_sprite(Size {w: 200, h: 200}); - sub_sprite.set_texture(texture.clone(), Some(Position {x: 350, y: 0}), 1.0); + let mut sub_sprite = canvas.create_texture_sprite(Size {w: 100, h: 100}); + sub_sprite.set_texture(texture.clone(), Some(Position {x: 0, y: 0}), 1.0); + sub_sprite.set_position(Position {x: 100, y: 100}); + sub_sprite.set_scale(2.0); + + let mut sub_sprite2 = canvas.create_texture_sprite(Size {w: 200, h: 200}); + sub_sprite2.set_texture(texture.clone(), Some(Position {x: 100, y: 0}), 1.0); + sub_sprite2.set_center(Center::Custom(Position {x:100, y:100})); + sub_sprite2.set_rotation(0.0); + sub_sprite2.set_position(Position {x: 100, y: 100}); + + let mut txt_sprite = canvas.create_text_sprite("00", Size {w: 100, h: 100}, 22.0); + txt_sprite.set_center(Center::BotLeft); + txt_sprite.set_position(Position {x:100, y:100}); + txt_sprite.set_bg_color(Color::WHITE); + txt_sprite.set_scale(1.0); canvas.clear(); canvas.update(); @@ -71,32 +93,54 @@ impl Application for ExampleApp { texture, tex_sprite, sub_sprite, + sub_sprite2, + txt_sprite, last_instant, last_offset: 0, - last_pos: Position::origin(), + last_pos: Position {x: 0, y: 0}, last_rot: 0.0, + frame_counter: 0, }) } fn tick(state: &mut ExampleState, canvas: &mut Canvas) -> Result<(), &'static str> { - use canvas::sprite::Sprite; + use canvas::{ + sprite::Sprite, + utils::Color, + }; let now = Instant::now(); let elapsed = now.duration_since(state.last_instant).as_millis(); state.last_instant = now; debug!("frame time: {}ms", elapsed); - + state.frame_counter += 1; + if state.frame_counter >= 60 { + state.frame_counter = 0; + state.txt_sprite.set_text(&format!("{}", elapsed)); + state.txt_sprite.set_text_size(elapsed as f32); + } + match state.frame_counter { + 0 => state.txt_sprite.set_fg_color(Color::RED), + 20 => state.txt_sprite.set_fg_color(Color::BLUE), + 40 => state.txt_sprite.set_fg_color(Color::GREEN), + _ => (), + } //state.sub_sprite.for_each(|pix| unsafe {pix.flat = pix.flat.wrapping_add(1);}); - state.last_offset += 1; - state.last_pos.y += 1; + // state.last_offset += 1; + //state.last_pos.x += 1; + //state.last_pos.y += 1; state.last_rot += 1.0; + debug!("{:#?}", state.last_pos); state.tex_sprite.set_texture(state.texture.clone(), None, state.last_rot/100.0); - state.sub_sprite.set_texture(state.texture.clone(), - Some(Position {x: state.last_offset, y: 0}), 1.0); - state.sub_sprite.set_position(state.last_pos); + // state.sub_sprite.set_texture(state.texture.clone(), + // Some(Position {x: state.last_offset, y: 0}), 1.0); +// state.sub_sprite.set_position(state.last_pos); +// state.sub_sprite2.set_position(state.last_pos); state.sub_sprite.set_rotation(state.last_rot); + state.sub_sprite2.set_rotation(state.last_rot + 45.0); +// state.txt_sprite.set_rotation(state.last_rot); //state.sub_sprite.set_scale(state.last_rot/1000.0); // inputs @@ -108,6 +152,7 @@ impl Application for ExampleApp { // unimplemented!(); //} // + // //match canvas.get_key_presses() { // Key::A => unimplemented!(), // _ => (), @@ -156,6 +201,8 @@ impl Application for ExampleApp { canvas.draw(&mut state.tex_sprite); canvas.draw(&mut state.sub_sprite); + canvas.draw(&mut state.sub_sprite2); + canvas.draw(&mut state.txt_sprite); canvas.update(); Ok(()) diff --git a/src/renderer.rs b/src/renderer.rs index c29662a..412255c 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -4,7 +4,7 @@ use log::{debug, error, info, trace, warn}; use raw_window_handle::HasRawWindowHandle; use wgpu; -use crate::utils::{Pixel, Size}; +use crate::utils::{Color, Size}; pub mod utils; @@ -96,7 +96,11 @@ impl WgpuRenderer { }) } - pub fn clear(&mut self, color: Pixel) { + pub fn get_surface_size(&self) -> Size { + self.surface_size + } + + pub fn clear(&mut self, color: Color) { let view = self.create_texture_view(); let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { @@ -221,16 +225,23 @@ impl WgpuRenderer { //--Renderer struct utils--------------------------------------------------------------------------- -impl From for wgpu::Color { +//impl From for wgpu::Color { +// +// fn from(value: Pixel) -> Self { +// Self { +// r: f64::from(value.r) / 255.0, +// g: f64::from(value.g) / 255.0, +// b: f64::from(value.b) / 255.0, +// a: f64::from(value.a) / 255.0, +// } +// } +//} - fn from(pix: Pixel) -> Self { - let rgba = pix.to_rgba(); - Self { - r: (rgba.r as f64) / 255.0, - g: (rgba.g as f64) / 255.0, - b: (rgba.b as f64) / 255.0, - a: (rgba.a as f64) / 255.0, - } - } +impl From for wgpu::Color { + + fn from(value: Color) -> Self { + value.into_wgpu_color() + } } + diff --git a/src/renderer/matrix.rs b/src/renderer/matrix.rs index ef825fb..549d9a6 100644 --- a/src/renderer/matrix.rs +++ b/src/renderer/matrix.rs @@ -16,8 +16,10 @@ pub const MATRIX_SIZE: usize = 48;//std::mem::size_of::>(); //--ModelMatrix struct------------------------------------------------------------------------------ pub struct ModelMatrix { position: Position, + center: Position, rotation: Deg, scale: f32, + aspect_matrix: Matrix3, uniform: Uniform, is_synced: bool, @@ -26,19 +28,27 @@ pub struct ModelMatrix { impl ModelMatrix { pub fn new(renderer: &WgpuRenderer, - pos: Position, - rot: f32, + position: Position, + rotation: f32, scale: f32) -> Self { + let surface_size: Vector2 = renderer.get_surface_size().into(); + //A factor 2 is used since the gpu coords go from -1.0 to 1.0 while the engine coords only + //go from 0 to 1 + let aspect_matrix = Matrix3::from_nonuniform_scale(2.0 / surface_size.x, + 2.0 / surface_size.y) + * Matrix3::from_translation(-surface_size / 2.0); - Self { - position: pos, - rotation: Deg (rot), - scale, + Self { + position, + center: Position::origin(), + rotation: Deg(rotation), + scale, + aspect_matrix, - uniform: Uniform::create(renderer, &renderer.matrix_layout), - is_synced: false, + uniform: Uniform::create(renderer, &renderer.matrix_layout), + is_synced: false, } } @@ -46,13 +56,8 @@ impl ModelMatrix { Self::new(renderer, Position::origin(), 0.0, 1.0) } - pub fn set_position(&mut self, pos: Position) { - self.position = pos; - self.is_synced = false; - } - - pub fn set_rotation(&mut self, rot: f32) { - self.rotation = Deg (rot); + pub fn set_position(&mut self, position: Position) { + self.position = position; self.is_synced = false; } @@ -61,22 +66,37 @@ impl ModelMatrix { self.is_synced = false; } + pub fn set_rotation(&mut self, rotation: f32) { + self.rotation = Deg(rotation); + self.is_synced = false; + } + + pub fn set_center(&mut self, center: Position) { + self.center = center; + self.is_synced = false; + } + pub fn get_uniform(&mut self) -> &mut Uniform { use cgmath::{Basis3, Rotation3}; if self.is_synced == false { - let pos_vec = Vector2 { - x: self.position.x as f32, - y: self.position.y as f32, - }; - let rotation_mat = Matrix3::from(Basis3::from_angle_z(self.rotation)); - let scale_mat = Matrix3::from_scale(self.scale); - let translation_mat = Matrix3::from_translation(pos_vec); - let aspect_mat = Matrix3::from_nonuniform_scale(1.0/1280.0, 1.0/720.0); + let position = Matrix3::from_translation(self.position.into()); + let scale = Matrix3::from_scale(self.scale); + let center = Matrix3::from_translation((self.center * -1.0).into()); + let rotation = Matrix3::from(Basis3::from_angle_z(self.rotation)); + + //to be read in reverse: scale is applied first, then the center offset is applied in + //reverse, so that it lines up with the world's origin. The rotation is then applied + //around the world's origin (and ence, the center). FInally, the translation is applied + //before the aspect matrix can move use from window-space (w x h) to normalized space + //(-1.0 to 1.0) + let matrix = self.aspect_matrix * position * rotation * center * scale; - let matrix = aspect_mat * translation_mat * rotation_mat * scale_mat; let mat_bytes: [u8; 36] = bytemuck::bytes_of(&matrix).try_into().unwrap(); + + //wgsl uses 16 bytes-aligned matrixes, but ours is 12 bytes-aligned. Fix that by adding + //4 paddings bytes after each 12 bytes let mut bytes = [0; 48]; for i in 0..12 { bytes[i] = mat_bytes[i]; diff --git a/src/renderer/utils.rs b/src/renderer/utils.rs index 80be958..cef883b 100644 --- a/src/renderer/utils.rs +++ b/src/renderer/utils.rs @@ -123,7 +123,8 @@ where } ); - let index_buffer = device.create_buffer( &wgpu_types::BufferDescriptor { + let index_buffer = device.create_buffer( + &wgpu_types::BufferDescriptor { label: Some("Index Buffer"), size: (I_NB * size_of::()) as u64, usage: Usages::INDEX | Usages::COPY_DST, diff --git a/src/sprite.rs b/src/sprite.rs index f8c0cf2..813f035 100644 --- a/src/sprite.rs +++ b/src/sprite.rs @@ -19,6 +19,16 @@ use crate::{ //--External imports-------------------------------------------------------------------------------- +//--Center enum------------------------------------------------------------------------------------- +pub enum Center { + Geometric, + TopLeft, + TopRight, + BotLeft, + BotRight, + Custom(Position), +} + //--Sprite trait------------------------------------------------------------------------------------ /// The interface for everything that can be rendered by the engine. @@ -27,19 +37,22 @@ use crate::{ /// well as the unique function necessary for it to be rendered pub trait Sprite { - /// Set the position of the [Sprite] on the screen - fn set_position(&mut self, pos: Position); + /// Sets the position of the [`Sprite`] on the screen + fn set_position(&mut self, position: Position); - /// Set the rotation of the [Sprite] on the screen - fn set_rotation(&mut self, rot: f32); + /// Sets the center of the [`Sprite`] for rotations as well as translations + fn set_center(&mut self, center: Center); - /// Set the alpha of the [Sprite] on the screen + /// Sets the rotation of the [`Sprite`] on the screen + fn set_rotation(&mut self, rotation: f32); + + /// Sets the alpha of the [`Sprite`] on the screen fn set_alpha(&mut self, alpha: u8); - /// Set the scale of the [Sprite] on the screen + /// Sets the scale of the [`Sprite`] on the screen fn set_scale(&mut self, scale: f32); - /// Renders the [Sprite] using the given rendering context + /// Renders the [`Sprite`] using the given rendering context fn render(&mut self, renderer: &mut WgpuRenderer); } diff --git a/src/sprite/shaders/texture.wgsl b/src/sprite/shaders/texture.wgsl index db20088..fb87586 100644 --- a/src/sprite/shaders/texture.wgsl +++ b/src/sprite/shaders/texture.wgsl @@ -14,12 +14,12 @@ struct VertexOutput { var model_matrix: mat3x3; @vertex -fn vs_main(model: VertexInput) -> VertexOutput { +fn vs_main(vertex: VertexInput) -> VertexOutput { var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.clip_position - = vec4(model_matrix * vec3(model.position, 1.0), 1.0); + out.tex_coords = vertex.tex_coords; + out.clip_position + = vec4(model_matrix * vec3(vertex.position, 1.0), 1.0); return out; } diff --git a/src/sprite/shape_sprite.rs b/src/sprite/shape_sprite.rs index a05ba0c..28fd027 100644 --- a/src/sprite/shape_sprite.rs +++ b/src/sprite/shape_sprite.rs @@ -5,7 +5,7 @@ use log::{debug, error, info, trace, warn}; use crate::{ renderer::WgpuRenderer, - sprite::Sprite, + sprite::{Sprite, Center}, shape::Shape, utils::{Pixel, Position, Size}, }; @@ -35,11 +35,15 @@ impl ShapeSprite { impl Sprite for ShapeSprite { - fn set_position(&mut self, _pos: Position) { + fn set_position(&mut self, _position: Position) { todo!(); } - fn set_rotation(&mut self, _rot: f32) { + fn set_center(&mut self, _center: Center) { + todo!(); + } + + fn set_rotation(&mut self, _rotation: f32) { todo!(); } diff --git a/src/sprite/text_sprite.rs b/src/sprite/text_sprite.rs index 1b30bc1..c1c5e91 100644 --- a/src/sprite/text_sprite.rs +++ b/src/sprite/text_sprite.rs @@ -5,53 +5,168 @@ use log::{debug, error, info, trace, warn}; use crate::{ renderer::WgpuRenderer, - sprite::Sprite, - utils::{Pixel, Position, Size}, + sprite::{Sprite, Center}, + utils::{Pixel, Color, Position, Size}, }; +use super::TextureSprite; //--External imports-------------------------------------------------------------------------------- -use cgmath::Matrix4; - //--TextSprite struct------------------------------------------------------------------------------- -#[allow(dead_code)] pub struct TextSprite { - matrix: Matrix4, - size: Size, - text: &'static str, //TODO: temporary + texture_sprite: TextureSprite, + + text: String, + text_size: f32, + fg_color: Color, + bg_color: Color, + + font: fontdue::Font, + layout: fontdue::layout::Layout, + text_generation_needed: bool, } impl TextSprite { - pub fn set_text(&mut self, _text: &'static str) { - todo!(); + pub fn new(text: &'static str, size: Size, text_size: f32, renderer: &WgpuRenderer) -> Self { + use crate::{ + renderer::Texture, + texture::TextureHandle, + }; + use fontdue::layout::{CoordinateSystem, Layout, TextStyle}; + + let font = Self::configure_font(text_size); + let mut layout = Layout::new(CoordinateSystem::PositiveYDown); + + //configure text layout to fit sprite size, keep default settings + layout.append(&[&font], &TextStyle::new(text, text_size, 0)); + + //draw text to the texture + let raw_texture : Vec:: + = vec![Color::NONE.into(); (size.h * size.w).try_into().unwrap()]; + + //create the texture + let texture = Texture::create(renderer, bytemuck::cast_slice(&raw_texture), size); + let texture_handle = TextureHandle::from_texture(texture); + + let texture_sprite = TextureSprite::new(texture_handle, size, &renderer); + + Self { + texture_sprite, + text: text.to_string(), + text_size, + fg_color: Color::BLACK, + bg_color: Color::NONE, + font, + layout, + text_generation_needed: true, + } } - pub fn set_color(&mut self, _color: Pixel) { - todo!(); + pub fn set_text(&mut self, text: &str) { + self.text = text.to_string(); + self.text_generation_needed = true; + } + + pub fn set_text_size(&mut self, text_size: f32) { + self.text_size = text_size; + self.text_generation_needed = true; + } + + pub fn set_fg_color(&mut self, color: Color) { + if self.fg_color != color { + self.fg_color = color; + self.text_generation_needed = true; + } + } + + pub fn set_bg_color(&mut self, color: Color) { + if self.bg_color != color { + self.bg_color = color; + self.text_generation_needed = true; + } + } + + fn generate_text(&mut self) { + + self.layout.clear(); + self.layout.append(&[&self.font], + &fontdue::layout::TextStyle::new(&self.text, self.text_size, 0)); + + let size = self.texture_sprite.get_size(); + self.texture_sprite.fill(self.bg_color.into()); + + //draw each glyph at the corresponding position on the texture + for glyph in self.layout.glyphs() { + let (_metrics, bitmap) = self.font.rasterize_config(glyph.key); + + let glyph_x = glyph.x as usize; + let glyph_y = glyph.y as usize; + let size_w = size.w as usize; + let size_h = size.h as usize; + + //only try to draw a glyph if its origin fits in the texture + if glyph_x < size_w && glyph_y < size_h { + + //glyphs that do not fit on the texture are cropped at the edge of the latter + for x in 0..std::cmp::min(glyph.width, size_w - glyph_x) { + for y in 0..std::cmp::min(glyph.height, size_h - glyph_y) { + + self.fg_color.set_alpha(bitmap[x + y * glyph.width]); + + self.texture_sprite.set_pixel( + Position { + x: (glyph_x + x) as i32, + y: (glyph_y + y) as i32, + }, + + Color::alpha_blend(&self.fg_color, &self.bg_color).into() + ); + } } } } + } + + fn configure_font(text_size: f32) -> fontdue::Font + { + let font = include_bytes!("../../assets/DejaVuSansMono.ttf"); + let settings = fontdue::FontSettings { + scale: text_size, + ..fontdue::FontSettings::default() + }; + fontdue::Font::from_bytes(&font[..], settings).unwrap() } } impl Sprite for TextSprite { - fn set_position(&mut self, _pos: Position) { - todo!(); + fn set_position(&mut self, position: Position) { + self.texture_sprite.set_position(position); } - fn set_rotation(&mut self, _rot: f32) { - todo!(); + fn set_center(&mut self, center: Center) { + self.texture_sprite.set_center(center); } - fn set_alpha(&mut self, _alpha: u8) { - todo!(); + fn set_rotation(&mut self, rotation: f32) { + self.texture_sprite.set_rotation(rotation); } - fn set_scale(&mut self, _scale: f32) { - todo!(); + fn set_alpha(&mut self, alpha: u8) { + self.texture_sprite.set_alpha(alpha); } - fn render(&mut self, _renderer: &mut WgpuRenderer) { - todo!(); + fn set_scale(&mut self, scale: f32) { + self.texture_sprite.set_scale(scale); + } + + fn render(&mut self, renderer: &mut WgpuRenderer) { + + // generate text if any setting changed + if self.text_generation_needed == true { + self.generate_text(); + self.text_generation_needed = false; + } + + self.texture_sprite.render(renderer); } } diff --git a/src/sprite/texture_sprite.rs b/src/sprite/texture_sprite.rs index 110ca7e..2794dd6 100644 --- a/src/sprite/texture_sprite.rs +++ b/src/sprite/texture_sprite.rs @@ -3,7 +3,7 @@ use log::{debug, error, info, trace, warn}; //--Internal imports-------------------------------------------------------------------------------- -use super::Sprite; +use super::{Sprite, Center}; use crate::{ texture::TextureHandle, @@ -91,11 +91,20 @@ impl TextureSprite { self.texture.for_each_in_area(func, self.offset, self.inner_size); } + pub fn fill(&mut self, pixel: Pixel) { + self.texture.fill(pixel); + } + + pub fn get_size(&self) -> Size { + self.texture.get_size() + } + fn update_vertices(&mut self, renderer: &WgpuRenderer) { debug!("Updating vertices..."); let size = self.texture.get_size(); + // compute normalized coordinates let x_size = self.inner_size.w as f32 / size.w as f32 * self.scale; let y_size = self.inner_size.h as f32 / size.h as f32 * self.scale; @@ -103,8 +112,10 @@ impl TextureSprite { let y_offset = self.offset.y as f32 / size.h as f32; // compute mesh size - let w = self.inner_size.w as f32; - let h = self.inner_size.h as f32; + + //divide by 2 since the quad's coords go from -0.5 to 0.5 + let w = self.inner_size.w as f32 / 2.0; + let h = self.inner_size.h as f32 / 2.0; let mesh = [ TextureVertex { @@ -133,12 +144,36 @@ impl TextureSprite { impl Sprite for TextureSprite { - fn set_position(&mut self, pos: Position) { - self.matrix.set_position(pos); + fn set_position(&mut self, position: Position) { + self.matrix.set_position(position); } - fn set_rotation(&mut self, rot: f32) { - self.matrix.set_rotation(rot); + fn set_center(&mut self, center: Center) { + let center_pos = match center { + Center::Geometric => Position {x: 0, y: 0}, + Center::TopLeft => Position { + x: -(self.inner_size.w as i32) / 2, + y: (self.inner_size.h as i32) / 2 + }, + Center::TopRight => Position { + x: (self.inner_size.w as i32) / 2, + y: (self.inner_size.h as i32) / 2 + }, + Center::BotLeft => Position { + x: -(self.inner_size.w as i32) / 2, + y: -(self.inner_size.h as i32) / 2 + }, + Center::BotRight => Position { + x: (self.inner_size.w as i32) / 2, + y: -(self.inner_size.h as i32) / 2 + }, + Center::Custom(pos) => pos, + }; + self.matrix.set_center(center_pos); + } + + fn set_rotation(&mut self, rotation: f32) { + self.matrix.set_rotation(rotation); } fn set_alpha(&mut self, _alpha: u8) { @@ -235,7 +270,7 @@ fn initialize_pipeline(renderer: &WgpuRenderer) -> wgpu::RenderPipeline { entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { format: renderer.config.format, - blend: Some(wgpu::BlendState::REPLACE), + blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], }), diff --git a/src/texture.rs b/src/texture.rs index 56ebf5b..8921dc1 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -30,7 +30,7 @@ impl TextureHandle { //TODO check pos ? let mut texture = self.texture.borrow_mut(); - let width = texture.size.w; + let width = texture.size.w as i32; texture.buffer[(pos.x + pos.y*width) as usize] = pix; texture.is_synced = false; } @@ -51,15 +51,21 @@ impl TextureHandle { { //TODO check offset and pos ? let mut texture = self.texture.borrow_mut(); - let width = texture.size.w; - for x in offset.x..(offset.x + size.w) { - for y in offset.y..(offset.y + size.h) { + let width = texture.size.w as i32; + for x in offset.x..(offset.x + size.w as i32) { + for y in offset.y..(offset.y + size.h as i32) { func(&mut texture.buffer[(x + y*width) as usize]); } } texture.is_synced = false; } + pub fn fill (&mut self, pixel: Pixel) { + let mut texture = self.texture.borrow_mut(); + texture.buffer.fill(pixel); + texture.is_synced = false; + } + pub fn texture(&self) -> &Rc> { //TODO improve that &self.texture @@ -70,4 +76,3 @@ impl TextureHandle { } } - diff --git a/src/utils.rs b/src/utils.rs index 4e5959e..df4d559 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,13 +3,13 @@ use log::{debug, error, info, trace, warn}; use winit; -use cgmath::Vector3; +use cgmath::{Vector3, Vector2}; //--Position struct--------------------------------------------------------------------------------- -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct Position { - pub x: u32, - pub y: u32, + pub x: i32, + pub y: i32, } impl Position { @@ -23,12 +23,19 @@ impl Position { } impl From for Vector3 { - + fn from(pos: Position) -> Self { Self::new(pos.x as f32, pos.y as f32, 0.0) } } +impl From for Vector2 { + + fn from(pos: Position) -> Self { + Self::new(pos.x as f32, pos.y as f32) + } +} + impl std::ops::Add for Position { type Output = Self; @@ -40,6 +47,17 @@ impl std::ops::Add for Position { } } +impl std::ops::Mul for Position { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Position { + x: ((self.x as f32) * rhs) as i32, + y: ((self.y as f32) * rhs) as i32, + } + } +} + //--Size struct------------------------------------------------------------------------------------- #[derive(Copy, Clone)] pub struct Size { @@ -52,7 +70,7 @@ impl From for winit::dpi::Size { fn from(size: Size) -> Self { winit::dpi::Size::Physical ( winit::dpi::PhysicalSize { - width: size.w, + width: size.w, height: size.h, }) } @@ -79,48 +97,118 @@ impl From> for Size { } } +impl From for Vector2 { + + fn from(size: Size) -> Self { + Self::new(size.w as f32, size.h as f32) + } +} + //--Pixel struct------------------------------------------------------------------------------------ -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Rgba { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, +#[repr(packed, C)] +#[derive(Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Pixel { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, } -#[derive(Copy, Clone, bytemuck::Zeroable)] -pub union Pixel { - pub flat: u32, - rgba: Rgba, -} +impl From for Pixel { -unsafe impl bytemuck::Pod for Pixel {} + fn from(value: Color) -> Self { + value.into_pixel() + } +} impl Pixel { - - pub fn rgb(r: u8, g: u8, b: u8) -> Pixel { - Pixel { - rgba: Rgba{r, g, b, a: 255}, - } - } - pub fn to_rgba(&self) -> Rgba { - unsafe { self.rgba } - } + pub const fn from_hex(hex: u32) -> Self { + let bytes = hex.to_be_bytes(); + Self { r: bytes[0], g: bytes[1], b: bytes[2], a: bytes[3] } + } + } //--Color values------------------------------------------------------------------------------------ #[non_exhaustive] -pub struct Color; - -impl Color { - pub const NONE: Pixel = Pixel {flat: 0x00000000}; - pub const WHITE: Pixel = Pixel {flat: 0xffffffff}; - pub const BLACK: Pixel = Pixel {flat: 0x000000ff}; - pub const RED: Pixel = Pixel {flat: 0xff0000ff}; - pub const GREEN: Pixel = Pixel {flat: 0x00ff00ff}; - pub const BLUE: Pixel = Pixel {flat: 0x0000ffff}; +#[derive(Copy, Clone, PartialEq)] +pub struct Color { + r: f32, + g: f32, + b: f32, + a: f32, } +impl From for Color { + + fn from(value: Pixel) -> Self { + Self { + r: f32::from(value.r) / 255.0, + g: f32::from(value.g) / 255.0, + b: f32::from(value.b) / 255.0, + a: f32::from(value.a) / 255.0, + } + } +} + +impl Color { + + pub fn set_alpha(&mut self, a: u8) { + self.a = f32::from(a) / 255.0; + } + + pub fn from_rgb(r: u8, g: u8, b: u8) -> Self { + Pixel { r, g, b, a: 255 }.into() + } + + pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self { + Pixel { r, g, b, a }.into() + } + + pub fn from_hex(hex: u32) -> Self { + Pixel::from_hex(hex).into() + } + + pub fn into_pixel(self) -> Pixel { + //unsafe block is used here for "to_int_unchecked" since the struct's invariant garentees + //that the values are within [0.0, 1.0], meaning we won't ever overflow an u8 + unsafe { + Pixel { + r: self.r.to_int_unchecked::() * 255, + g: self.g.to_int_unchecked::() * 255, + b: self.b.to_int_unchecked::() * 255, + a: self.a.to_int_unchecked::() * 255, + } + } + } + + pub fn into_wgpu_color(self) -> wgpu::Color { + wgpu::Color { + r: f64::from(self.r), + g: f64::from(self.g), + b: f64::from(self.b), + a: f64::from(self.a), + } + } + + pub fn alpha_blend(&self, other: &Self) -> Self { + //apply alpha compisting's "over" operator, see + //[https://en.wikipedia.org/wiki/Alpha_compositing] for more detail + let a = self.a + other.a * (1.0 - self.a); + Self { + r: (self.r * self.a + other.r * other.a * (1.0 - self.a)) / a, + g: (self.g * self.a + other.g * other.a * (1.0 - self.a)) / a, + b: (self.b * self.a + other.b * other.a * (1.0 - self.a)) / a, + a, + } + } + + pub const NONE: Self = Self {r: 0.0, g: 0.0, b: 0.0, a: 0.0}; + pub const WHITE: Self = Self {r: 1.0, g: 1.0, b: 1.0, a: 1.0}; + pub const BLACK: Self = Self {r: 0.0, g: 0.0, b: 0.0, a: 1.0}; + pub const RED: Self = Self {r: 1.0, g: 0.0, b: 0.0, a: 1.0}; + pub const GREEN: Self = Self {r: 0.0, g: 1.0, b: 0.0, a: 1.0}; + pub const BLUE: Self = Self {r: 0.0, g: 0.0, b: 1.0, a: 1.0}; +}