diff --git a/Cargo.toml b/Cargo.toml index 9458a05..2724111 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,6 @@ members = [ "./crates/comet_input", "./crates/comet_log", "./crates/comet_ui", - "./crates/comet_fonts", "./crates/comet_sound", "./crates/comet_structs" ] @@ -63,4 +62,4 @@ comet_resources = { path = "./crates/comet_resources", workspace = true } comet_ecs = { path = "./crates/comet_ecs", workspace = true } comet_input = { path = "./crates/comet_input", workspace = true } comet_log = { path = "./crates/comet_log", workspace = true } -comet_structs = { path = "./crates/comet_structs", workspace = true } +comet_structs = { path = "./crates/comet_structs", workspace = true } \ No newline at end of file diff --git a/build.rs b/build.rs deleted file mode 100644 index ee9b289..0000000 --- a/build.rs +++ /dev/null @@ -1,22 +0,0 @@ -use anyhow::*; -use fs_extra::copy_items; -use fs_extra::dir::CopyOptions; -use std::env; - - -fn main() -> Result<()> { - // This tells cargo to rerun this script if something in /resources/ changes. - println!("cargo:rerun-if-changed=resources/textures/*"); - println!("cargo:rerun-if-changed=resources/shaders/*"); - - let out_dir = env::var("OUT_DIR")?; - let mut copy_options = CopyOptions::new(); - copy_options.overwrite = true; - let mut paths_to_copy = Vec::new(); - paths_to_copy.push("resources/textures/"); - paths_to_copy.push("resources/shaders/"); - - copy_items(&paths_to_copy, out_dir, ©_options)?; - - Ok(()) -} diff --git a/crates/comet_ecs/src/component.rs b/crates/comet_ecs/src/component.rs index 78e260c..bc4dcc3 100644 --- a/crates/comet_ecs/src/component.rs +++ b/crates/comet_ecs/src/component.rs @@ -57,6 +57,13 @@ pub struct Camera2D { priority: u8 } +#[derive(Component)] +pub struct Text { + content: &'static str, + font: &'static str, + font_size: f32, +} + // ################################################## // # BUNDLES # // ################################################## diff --git a/crates/comet_fonts/Cargo.toml b/crates/comet_fonts/Cargo.toml deleted file mode 100644 index c8e8648..0000000 --- a/crates/comet_fonts/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "comet_fonts" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/crates/comet_fonts/src/lib.rs b/crates/comet_fonts/src/lib.rs deleted file mode 100644 index b93cf3f..0000000 --- a/crates/comet_fonts/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs index e67e099..1d1f6b5 100644 --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -11,7 +11,7 @@ use comet_colors::LinearRgba; use comet_ecs::{Camera, Camera2D, Component, Position2D, Render, Render2D, Transform2D, Scene}; use comet_log::{debug, info}; use comet_math::{Point3, Vec2, Vec3}; -use comet_resources::{texture, graphic_resource_manager::GraphicResorceManager, Texture, Vertex}; +use comet_resources::{texture, graphic_resource_manager::GraphicResourceManager, Texture, Vertex}; use comet_resources::texture_atlas::TextureRegion; use comet_structs::ComponentSet; use crate::camera::{RenderCamera, CameraUniform}; @@ -37,7 +37,7 @@ pub struct Renderer2D<'a> { clear_color: Color, diffuse_texture: Texture, diffuse_bind_group: wgpu::BindGroup, - graphic_resource_manager: GraphicResorceManager, + graphic_resource_manager: GraphicResourceManager, camera: RenderCamera, camera_uniform: CameraUniform, camera_buffer: wgpu::Buffer, @@ -117,7 +117,7 @@ impl<'a> Renderer2D<'a> { let num_indices = index_data.len() as u32; - let graphic_resource_manager = GraphicResorceManager::new(); + let graphic_resource_manager = GraphicResourceManager::new(); let diffuse_bytes = include_bytes!(r"../../../resources/textures/comet_icon.png"); let diffuse_texture = @@ -540,6 +540,10 @@ impl<'a> Renderer2D<'a> { info!("Applied base shader!"); } + pub fn load_font(&mut self, path: &str, size: f32) { + self.graphic_resource_manager.load_font(path, size); + } + /// An interface for getting the location of the texture in the texture atlas. pub fn get_texture_region(&self, texture_path: String) -> &TextureRegion { assert!(self.graphic_resource_manager.texture_atlas().textures().contains_key(&texture_path), "Texture not found in atlas"); @@ -741,7 +745,34 @@ impl<'a> Renderer2D<'a> { /// A function to draw text at a given position. pub fn draw_text_at(&mut self, text: &str, position: Point3) { - todo!() + let mut x = position.x(); + let mut y = position.y(); + + for c in text.chars() { + let region = self.get_texture_region(c.to_string()); + let (dim_x, dim_y) = region.dimensions(); + + let (bound_x, bound_y) = + ((dim_x as f32/ self.config.width as f32) * 0.5, (dim_y as f32/ self.config.height as f32) * 0.5); + + let vertices: &mut Vec = &mut vec![ + Vertex :: new ( [-bound_x + x, bound_y + y, 0.0], [region.x0(), region.y0()], [0.0, 0.0, 0.0, 0.0] ), + Vertex :: new ( [-bound_x + x, -bound_y + y, 0.0], [region.x0(), region.y1()], [0.0, 0.0, 0.0, 0.0] ), + Vertex :: new ( [ bound_x + x, -bound_y + y, 0.0], [region.x1(), region.y1()], [0.0, 0.0, 0.0, 0.0] ) , + Vertex :: new ( [ bound_x + x, bound_y + y, 0.0], [region.x1(), region.y0()], [0.0, 0.0, 0.0, 0.0] ) + ]; + + let buffer_size = self.vertex_data.len() as u16; + + let indices: &mut Vec = &mut vec![ + 0 + buffer_size, 1 + buffer_size, 3 + buffer_size, + 1 + buffer_size, 2 + buffer_size, 3 + buffer_size + ]; + + self.push_to_buffers(vertices, indices); + + x += dim_x as f32; + } } fn find_priority_camera(&self, cameras: Vec) -> usize { @@ -779,7 +810,7 @@ impl<'a> Renderer2D<'a> { let camera_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Camera Buffer"), contents: bytemuck::cast_slice(&[camera_uniform]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, }); let camera_bind_group_layout = diff --git a/crates/comet_renderer/src/texture.rs b/crates/comet_renderer/src/texture.rs deleted file mode 100644 index 4042061..0000000 --- a/crates/comet_renderer/src/texture.rs +++ /dev/null @@ -1,254 +0,0 @@ -use anyhow::*; -use image::GenericImageView; - -pub struct Texture { - #[allow(unused)] - pub texture: wgpu::Texture, - pub view: wgpu::TextureView, - pub sampler: wgpu::Sampler, - pub size: wgpu::Extent3d, -} - -impl Texture { - pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; - - pub fn create_depth_texture( - device: &wgpu::Device, - config: &wgpu::SurfaceConfiguration, - label: &str, - ) -> Self { - let size = wgpu::Extent3d { - width: config.width.max(1), - height: config.height.max(1), - depth_or_array_layers: 1, - }; - let desc = wgpu::TextureDescriptor { - label: Some(label), - size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: Self::DEPTH_FORMAT, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[Self::DEPTH_FORMAT], - }; - let texture = device.create_texture(&desc); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Nearest, - compare: Some(wgpu::CompareFunction::LessEqual), - lod_min_clamp: 0.0, - lod_max_clamp: 100.0, - ..Default::default() - }); - - Self { - texture, - view, - sampler, - size, // NEW! - } - } - - #[allow(dead_code)] - pub fn from_bytes( - device: &wgpu::Device, - queue: &wgpu::Queue, - bytes: &[u8], - label: &str, - is_normal_map: bool, - ) -> Result { - let img = image::load_from_memory(bytes)?; - Self::from_image(device, queue, &img, Some(label), is_normal_map) - } - - pub fn from_image( - device: &wgpu::Device, - queue: &wgpu::Queue, - img: &image::DynamicImage, - label: Option<&str>, - is_normal_map: bool, - ) -> Result { - let dimensions = img.dimensions(); - let rgba = img.to_rgba8(); - - let format = if is_normal_map { - wgpu::TextureFormat::Rgba8Unorm - } else { - wgpu::TextureFormat::Rgba8UnormSrgb - }; - let usage = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST; - let size = wgpu::Extent3d { - width: img.width(), - height: img.height(), - depth_or_array_layers: 1, - }; - let texture = Self::create_2d_texture( - device, - size.width, - size.height, - format, - usage, - wgpu::FilterMode::Linear, - label, - ); - - queue.write_texture( - wgpu::ImageCopyTexture { - aspect: wgpu::TextureAspect::All, - texture: &texture.texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - }, - &rgba, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4 * dimensions.0), - rows_per_image: Some(dimensions.1), - }, - size, - ); - - Ok(texture) - } - - pub(crate) fn create_2d_texture( - device: &wgpu::Device, - width: u32, - height: u32, - format: wgpu::TextureFormat, - usage: wgpu::TextureUsages, - mag_filter: wgpu::FilterMode, - label: Option<&str>, - ) -> Self { - let size = wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }; - Self::create_texture( - device, - label, - size, - format, - usage, - wgpu::TextureDimension::D2, - mag_filter, - ) - } - - pub fn create_texture( - device: &wgpu::Device, - label: Option<&str>, - size: wgpu::Extent3d, - format: wgpu::TextureFormat, - usage: wgpu::TextureUsages, - dimension: wgpu::TextureDimension, - mag_filter: wgpu::FilterMode, - ) -> Self { - let texture = device.create_texture(&wgpu::TextureDescriptor { - label, - size, - mip_level_count: 1, - sample_count: 1, - dimension, - format, - usage, - view_formats: &[], - }); - - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); - - Self { - texture, - view, - sampler, - size, // NEW! - } - } -} - -pub struct CubeTexture { - texture: wgpu::Texture, - sampler: wgpu::Sampler, - view: wgpu::TextureView, -} - -impl CubeTexture { - pub fn create_2d( - device: &wgpu::Device, - width: u32, - height: u32, - format: wgpu::TextureFormat, - mip_level_count: u32, - usage: wgpu::TextureUsages, - mag_filter: wgpu::FilterMode, - label: Option<&str>, - ) -> Self { - let texture = device.create_texture(&wgpu::TextureDescriptor { - label, - size: wgpu::Extent3d { - width, - height, - // A cube has 6 sides, so we need 6 layers - depth_or_array_layers: 6, - }, - mip_level_count, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format, - usage, - view_formats: &[], - }); - - let view = texture.create_view(&wgpu::TextureViewDescriptor { - label, - dimension: Some(wgpu::TextureViewDimension::Cube), - array_layer_count: Some(6), - ..Default::default() - }); - - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label, - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); - - Self { - texture, - sampler, - view, - } - } - - pub fn texture(&self) -> &wgpu::Texture { - &self.texture - } - - pub fn view(&self) -> &wgpu::TextureView { - &self.view - } - - pub fn sampler(&self) -> &wgpu::Sampler { - &self.sampler - } -} \ No newline at end of file diff --git a/crates/comet_resources/Cargo.toml b/crates/comet_resources/Cargo.toml index 3e481fa..a2947ed 100644 --- a/crates/comet_resources/Cargo.toml +++ b/crates/comet_resources/Cargo.toml @@ -12,6 +12,7 @@ anyhow = "1.0" tobj = { version = "3.2", default-features = false, features = ["async"]} bytemuck = { version = "1.16", features = [ "derive" ] } log = "0.4.22" +ab_glyph = "0.2.29" [dependencies.image] version = "0.24" diff --git a/crates/comet_resources/src/font.rs b/crates/comet_resources/src/font.rs new file mode 100644 index 0000000..417970e --- /dev/null +++ b/crates/comet_resources/src/font.rs @@ -0,0 +1,85 @@ +use image::{DynamicImage, Rgba, RgbaImage}; +use ab_glyph::{FontArc, PxScale, ScaleFont, Glyph, point, Font as AbFont}; + +pub struct Font { + name: String, + glyphs: Vec, +} + +impl Font { + pub fn new(path: &str, size: f32) -> Self { + Font { + name: path.to_string(), + glyphs: Self::generate_images(path, size), + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn names(&self) -> Vec { + let mut names = Vec::new(); + for code_point in 0x0020..=0x007E { + if let Some(ch) = std::char::from_u32(code_point) { + names.push(ch.to_string()); + } + } + names + } + + pub fn glyph(&self, index: usize) -> &DynamicImage { + &self.glyphs[index] + } + + pub fn glyphs(&self) -> Vec { + self.glyphs.clone() + } + + fn generate_images(path: &str, size: f32) -> Vec { + let font_data = std::fs::read(path).expect("Failed to read font file"); + let font = FontArc::try_from_vec(font_data).expect("Failed to load font"); + + let scale = PxScale::from(size); + let scaled_font = font.as_scaled(scale); + + let mut images = Vec::new(); + + for code_point in 0x0020..=0x007E { + if let Some(ch) = std::char::from_u32(code_point) { + if font.glyph_id(ch).0 == 0 { + continue; + } + + let glyph = Glyph { + id: font.glyph_id(ch), + scale, + position: point(0.0, 0.0), + }; + + if let Some(outline) = scaled_font.outline_glyph(glyph) { + let bounds = outline.px_bounds(); + let width = bounds.width().ceil() as u32; + let height = bounds.height().ceil() as u32; + + if width == 0 || height == 0 { + continue; + } + + let mut image = RgbaImage::new(width, height); + for pixel in image.pixels_mut() { + *pixel = Rgba([0, 0, 0, 0]); + } + + outline.draw(|x, y, v| { + let alpha = (v * 255.0) as u8; + image.put_pixel(x, y, Rgba([255, 255, 255, alpha])); + }); + + images.push(DynamicImage::ImageRgba8(image)); + } + } + } + images + } +} \ No newline at end of file diff --git a/crates/comet_resources/src/graphic_resource_manager.rs b/crates/comet_resources/src/graphic_resource_manager.rs index ee3127b..9d716ef 100644 --- a/crates/comet_resources/src/graphic_resource_manager.rs +++ b/crates/comet_resources/src/graphic_resource_manager.rs @@ -4,19 +4,21 @@ use std::{ use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages}; use wgpu::naga::ShaderStage; -use crate::{texture, Texture}; +use crate::{font, texture, Texture}; use crate::texture_atlas::{TextureAtlas, TextureRegion}; -pub struct GraphicResorceManager { +pub struct GraphicResourceManager { texture_atlas: TextureAtlas, + fonts: HashMap, data_files: HashMap, shaders: HashMap } -impl GraphicResorceManager { +impl GraphicResourceManager { pub fn new() -> Self { Self { texture_atlas: TextureAtlas::empty(), + fonts: HashMap::new(), data_files: HashMap::new(), shaders: HashMap::new() } @@ -119,6 +121,12 @@ impl GraphicResorceManager { self.shaders.get(shader) } + pub fn load_font(&mut self, path: &str, size: f32) { + let font = font::Font::new(path, size); + let atlas = TextureAtlas::from_textures(font.glyphs(), font.names()); + self.fonts.insert(font.name().to_string(), atlas); + } + /*pub async fn load_model( &self, file_name: &str, diff --git a/crates/comet_resources/src/lib.rs b/crates/comet_resources/src/lib.rs index f0f0152..e2e81f2 100644 --- a/crates/comet_resources/src/lib.rs +++ b/crates/comet_resources/src/lib.rs @@ -8,6 +8,7 @@ pub mod vertex; pub mod texture_atlas; pub mod graphic_resource_manager; mod material; +mod font; /*use std::io::{BufReader, Cursor}; use wgpu::util::DeviceExt; diff --git a/crates/comet_resources/src/texture_atlas.rs b/crates/comet_resources/src/texture_atlas.rs index 75773aa..6595384 100644 --- a/crates/comet_resources/src/texture_atlas.rs +++ b/crates/comet_resources/src/texture_atlas.rs @@ -112,8 +112,6 @@ impl TextureAtlas { pub fn from_texture_paths( paths: Vec, ) -> Self { - //let t0 = Instant::now(); - let mut textures: Vec = Vec::new(); let mut regions: HashMap = HashMap::new(); @@ -147,7 +145,7 @@ impl TextureAtlas { x_offset = 0; previous = texture.height(); } - //base.copy_from(texture, x_offset, y_offset).expect("Nope, you propably failed the offets"); + Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); regions.insert(path.to_string(), TextureRegion::new( x_offset as f32 / width as f32, @@ -159,16 +157,56 @@ impl TextureAtlas { x_offset += texture.width(); } - // Save the image to disk as a PNG - //let output_path = Path::new(r"C:\Users\lisk77\Code Sharing\comet-engine\resources\textures\atlas.png"); - //base.save_with_format(output_path, ImageFormat::Png).expect("Failed to save texture atlas"); + info!("Texture atlas created!"); + + TextureAtlas { + atlas: base, + textures: regions + } + } + + pub fn from_textures( + textures: Vec, + names: Vec, + ) -> Self { + let mut regions: HashMap = HashMap::new(); + + info!("Sorting textures by height..."); + + let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = textures.iter().zip(names.iter()).collect(); + texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); + let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = texture_path_pairs.into_iter().unzip(); + let sorted_textures: Vec = sorted_textures.into_iter().map(|t| t.clone()).collect(); + let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); + + let (height, width) = (Self::calculate_atlas_height(&sorted_textures), Self::calculate_atlas_width(&sorted_textures)); + let mut base = DynamicImage::new_rgba8(width,height); + + let mut previous = sorted_textures.get(0).unwrap().height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; + + info!("Creating texture atlas..."); + + for (texture, name) in sorted_textures.iter().zip(sorted_paths.iter()) { + if texture.height() != previous { + y_offset += previous; + x_offset = 0; + previous = texture.height(); + } + + Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); + regions.insert(name.to_string(), TextureRegion::new( + x_offset as f32 / width as f32, + y_offset as f32 / height as f32, + (x_offset + texture.width()) as f32 / width as f32, + (y_offset + texture.height()) as f32 / height as f32, + texture.dimensions() + )); + x_offset += texture.width(); + } info!("Texture atlas created!"); - //debug!(format!("{:?}", regions)); - - /*let t1 = Instant::now(); - let delta = t1.duration_since(t0); - println!("{:?}", delta);*/ TextureAtlas { atlas: base,