Compare commits

...

12 Commits

Author SHA1 Message Date
Steins7
92e5ff1082 Merge pull request 'cpu_text_sprite' (#1) from cpu_text_sprite into master
Reviewed-on: https://git.steins7.ovh/Steins7/canvas/pulls/1
2024-03-02 14:45:49 +00:00
1cb6d7fc72 Rework Pixel and Color to implement alpha blending 2024-03-02 14:45:22 +00:00
d10ce2552d Add new Center enum for center config 2024-03-02 14:45:22 +00:00
e921ea74d9 Update example 2024-03-02 14:45:22 +00:00
765f7ea9e6 Minor visual code's cleanup 2024-03-02 14:45:22 +00:00
1d4de23391 Fix matrix transformation issues
This time, the issues should be fixed for good. Everything behaves as expected.
The only issue remaining is that of the text sprite not having the right size
2024-03-02 14:45:22 +00:00
f6e0619be0 Add sprite center configuration
For now, the center is juste defined by coordinates in a haphazard way,
this will need a proper implementation at some point
2024-03-02 14:45:22 +00:00
3a630fefaf Fix matrix transformation issues 2024-03-02 14:45:22 +00:00
32bfe01a1e Implement text sprite main functions 2024-03-02 14:45:22 +00:00
fabe090307 Update .gitignore 2024-03-02 14:45:22 +00:00
d334ee8f98 Implement cpu-generated text sprite 2024-03-02 14:45:22 +00:00
5aa0a70f5f Add editorconfig file 2024-03-02 15:00:48 +01:00
18 changed files with 525 additions and 142 deletions

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
[*.rs]
indent_style = tab
indent_size = 4
max_line_length = 100

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
/target
*.log
*.bkp
*.perf
*.data
*.html

30
Cargo.lock generated
View File

@ -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"

View File

@ -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

BIN
assets/DejaVuSansMono.ttf Normal file

Binary file not shown.

View File

@ -28,7 +28,7 @@ pub trait Application<S> {
//--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;
}

View File

@ -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()?;

View File

@ -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<ExampleState> for ExampleApp {
fn init(canvas: &mut Canvas) -> Result<ExampleState, &'static str> {
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<ExampleState> 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<ExampleState> for ExampleApp {
// unimplemented!();
//}
//
//
//match canvas.get_key_presses() {
// Key::A => unimplemented!(),
// _ => (),
@ -156,6 +201,8 @@ impl Application<ExampleState> 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(())

View File

@ -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<Pixel> for wgpu::Color {
//impl From<Pixel> 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<Color> for wgpu::Color {
fn from(value: Color) -> Self {
value.into_wgpu_color()
}
}

View File

@ -16,8 +16,10 @@ pub const MATRIX_SIZE: usize = 48;//std::mem::size_of::<Matrix3<f32>>();
//--ModelMatrix struct------------------------------------------------------------------------------
pub struct ModelMatrix {
position: Position,
center: Position,
rotation: Deg<f32>,
scale: f32,
aspect_matrix: Matrix3<f32>,
uniform: Uniform<MATRIX_SIZE>,
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<f32> = 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<MATRIX_SIZE> {
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];

View File

@ -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::<u16>()) as u64,
usage: Usages::INDEX | Usages::COPY_DST,

View File

@ -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);
}

View File

@ -14,12 +14,12 @@ struct VertexOutput {
var<uniform> model_matrix: mat3x3<f32>;
@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<f32>(model_matrix * vec3<f32>(model.position, 1.0), 1.0);
out.tex_coords = vertex.tex_coords;
out.clip_position
= vec4<f32>(model_matrix * vec3<f32>(vertex.position, 1.0), 1.0);
return out;
}

View File

@ -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!();
}

View File

@ -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<f32>,
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::<Pixel>
= 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);
}
}

View File

@ -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,
})],
}),

View File

@ -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<RefCell<Texture>> {
//TODO improve that
&self.texture
@ -70,4 +76,3 @@ impl TextureHandle {
}
}

View File

@ -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<Position> for Vector3<f32> {
fn from(pos: Position) -> Self {
Self::new(pos.x as f32, pos.y as f32, 0.0)
}
}
impl From<Position> for Vector2<f32> {
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<f32> 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<Size> 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<winit::dpi::PhysicalSize<u32>> for Size {
}
}
impl From<Size> for Vector2<f32> {
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<Color> 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<Pixel> 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::<u8>() * 255,
g: self.g.to_int_unchecked::<u8>() * 255,
b: self.b.to_int_unchecked::<u8>() * 255,
a: self.a.to_int_unchecked::<u8>() * 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};
}