mirror of
https://github.com/lisk77/comet.git
synced 2025-10-23 21:38:50 +00:00
feat: added Font
to get glyphs out of ttf files and make a TextureAtlas
with them (right now only latin range of Unicode to not explode the atlas) and started trying to incorporate text rendering in ECS and Renderer2D
This commit is contained in:
parent
5430ee0d7e
commit
9e16179df3
12 changed files with 191 additions and 317 deletions
|
@ -49,7 +49,6 @@ members = [
|
||||||
"./crates/comet_input",
|
"./crates/comet_input",
|
||||||
"./crates/comet_log",
|
"./crates/comet_log",
|
||||||
"./crates/comet_ui",
|
"./crates/comet_ui",
|
||||||
"./crates/comet_fonts",
|
|
||||||
"./crates/comet_sound",
|
"./crates/comet_sound",
|
||||||
"./crates/comet_structs"
|
"./crates/comet_structs"
|
||||||
]
|
]
|
||||||
|
|
22
build.rs
22
build.rs
|
@ -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(())
|
|
||||||
}
|
|
|
@ -57,6 +57,13 @@ pub struct Camera2D {
|
||||||
priority: u8
|
priority: u8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Text {
|
||||||
|
content: &'static str,
|
||||||
|
font: &'static str,
|
||||||
|
font_size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
// ##################################################
|
// ##################################################
|
||||||
// # BUNDLES #
|
// # BUNDLES #
|
||||||
// ##################################################
|
// ##################################################
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "comet_fonts"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,7 +11,7 @@ use comet_colors::LinearRgba;
|
||||||
use comet_ecs::{Camera, Camera2D, Component, Position2D, Render, Render2D, Transform2D, Scene};
|
use comet_ecs::{Camera, Camera2D, Component, Position2D, Render, Render2D, Transform2D, Scene};
|
||||||
use comet_log::{debug, info};
|
use comet_log::{debug, info};
|
||||||
use comet_math::{Point3, Vec2, Vec3};
|
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_resources::texture_atlas::TextureRegion;
|
||||||
use comet_structs::ComponentSet;
|
use comet_structs::ComponentSet;
|
||||||
use crate::camera::{RenderCamera, CameraUniform};
|
use crate::camera::{RenderCamera, CameraUniform};
|
||||||
|
@ -37,7 +37,7 @@ pub struct Renderer2D<'a> {
|
||||||
clear_color: Color,
|
clear_color: Color,
|
||||||
diffuse_texture: Texture,
|
diffuse_texture: Texture,
|
||||||
diffuse_bind_group: wgpu::BindGroup,
|
diffuse_bind_group: wgpu::BindGroup,
|
||||||
graphic_resource_manager: GraphicResorceManager,
|
graphic_resource_manager: GraphicResourceManager,
|
||||||
camera: RenderCamera,
|
camera: RenderCamera,
|
||||||
camera_uniform: CameraUniform,
|
camera_uniform: CameraUniform,
|
||||||
camera_buffer: wgpu::Buffer,
|
camera_buffer: wgpu::Buffer,
|
||||||
|
@ -117,7 +117,7 @@ impl<'a> Renderer2D<'a> {
|
||||||
|
|
||||||
let num_indices = index_data.len() as u32;
|
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_bytes = include_bytes!(r"../../../resources/textures/comet_icon.png");
|
||||||
let diffuse_texture =
|
let diffuse_texture =
|
||||||
|
@ -540,6 +540,10 @@ impl<'a> Renderer2D<'a> {
|
||||||
info!("Applied base shader!");
|
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.
|
/// An interface for getting the location of the texture in the texture atlas.
|
||||||
pub fn get_texture_region(&self, texture_path: String) -> &TextureRegion {
|
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");
|
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.
|
/// A function to draw text at a given position.
|
||||||
pub fn draw_text_at(&mut self, text: &str, position: Point3) {
|
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<Vertex> = &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<u16> = &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<Camera2D>) -> usize {
|
fn find_priority_camera(&self, cameras: Vec<Camera2D>) -> usize {
|
||||||
|
@ -779,7 +810,7 @@ impl<'a> Renderer2D<'a> {
|
||||||
let camera_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let camera_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
label: Some("Camera Buffer"),
|
label: Some("Camera Buffer"),
|
||||||
contents: bytemuck::cast_slice(&[camera_uniform]),
|
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 =
|
let camera_bind_group_layout =
|
||||||
|
|
|
@ -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<Self> {
|
|
||||||
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<Self> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,6 +12,7 @@ anyhow = "1.0"
|
||||||
tobj = { version = "3.2", default-features = false, features = ["async"]}
|
tobj = { version = "3.2", default-features = false, features = ["async"]}
|
||||||
bytemuck = { version = "1.16", features = [ "derive" ] }
|
bytemuck = { version = "1.16", features = [ "derive" ] }
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
|
ab_glyph = "0.2.29"
|
||||||
|
|
||||||
[dependencies.image]
|
[dependencies.image]
|
||||||
version = "0.24"
|
version = "0.24"
|
||||||
|
|
85
crates/comet_resources/src/font.rs
Normal file
85
crates/comet_resources/src/font.rs
Normal file
|
@ -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<DynamicImage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String> {
|
||||||
|
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<DynamicImage> {
|
||||||
|
self.glyphs.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_images(path: &str, size: f32) -> Vec<DynamicImage> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,19 +4,21 @@ use std::{
|
||||||
|
|
||||||
use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages};
|
use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages};
|
||||||
use wgpu::naga::ShaderStage;
|
use wgpu::naga::ShaderStage;
|
||||||
use crate::{texture, Texture};
|
use crate::{font, texture, Texture};
|
||||||
use crate::texture_atlas::{TextureAtlas, TextureRegion};
|
use crate::texture_atlas::{TextureAtlas, TextureRegion};
|
||||||
|
|
||||||
pub struct GraphicResorceManager {
|
pub struct GraphicResourceManager {
|
||||||
texture_atlas: TextureAtlas,
|
texture_atlas: TextureAtlas,
|
||||||
|
fonts: HashMap<String, TextureAtlas>,
|
||||||
data_files: HashMap<String, String>,
|
data_files: HashMap<String, String>,
|
||||||
shaders: HashMap<String, ShaderModule>
|
shaders: HashMap<String, ShaderModule>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphicResorceManager {
|
impl GraphicResourceManager {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
texture_atlas: TextureAtlas::empty(),
|
texture_atlas: TextureAtlas::empty(),
|
||||||
|
fonts: HashMap::new(),
|
||||||
data_files: HashMap::new(),
|
data_files: HashMap::new(),
|
||||||
shaders: HashMap::new()
|
shaders: HashMap::new()
|
||||||
}
|
}
|
||||||
|
@ -119,6 +121,12 @@ impl GraphicResorceManager {
|
||||||
self.shaders.get(shader)
|
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(
|
/*pub async fn load_model(
|
||||||
&self,
|
&self,
|
||||||
file_name: &str,
|
file_name: &str,
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub mod vertex;
|
||||||
pub mod texture_atlas;
|
pub mod texture_atlas;
|
||||||
pub mod graphic_resource_manager;
|
pub mod graphic_resource_manager;
|
||||||
mod material;
|
mod material;
|
||||||
|
mod font;
|
||||||
/*use std::io::{BufReader, Cursor};
|
/*use std::io::{BufReader, Cursor};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
|
|
@ -112,8 +112,6 @@ impl TextureAtlas {
|
||||||
pub fn from_texture_paths(
|
pub fn from_texture_paths(
|
||||||
paths: Vec<String>,
|
paths: Vec<String>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
//let t0 = Instant::now();
|
|
||||||
|
|
||||||
let mut textures: Vec<DynamicImage> = Vec::new();
|
let mut textures: Vec<DynamicImage> = Vec::new();
|
||||||
let mut regions: HashMap<String, TextureRegion> = HashMap::new();
|
let mut regions: HashMap<String, TextureRegion> = HashMap::new();
|
||||||
|
|
||||||
|
@ -147,7 +145,7 @@ impl TextureAtlas {
|
||||||
x_offset = 0;
|
x_offset = 0;
|
||||||
previous = texture.height();
|
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);
|
Self::insert_texture_at(&mut base, &texture, x_offset, y_offset);
|
||||||
regions.insert(path.to_string(), TextureRegion::new(
|
regions.insert(path.to_string(), TextureRegion::new(
|
||||||
x_offset as f32 / width as f32,
|
x_offset as f32 / width as f32,
|
||||||
|
@ -159,16 +157,56 @@ impl TextureAtlas {
|
||||||
x_offset += texture.width();
|
x_offset += texture.width();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the image to disk as a PNG
|
info!("Texture atlas created!");
|
||||||
//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");
|
TextureAtlas {
|
||||||
|
atlas: base,
|
||||||
|
textures: regions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_textures(
|
||||||
|
textures: Vec<DynamicImage>,
|
||||||
|
names: Vec<String>,
|
||||||
|
) -> Self {
|
||||||
|
let mut regions: HashMap<String, TextureRegion> = 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<DynamicImage> = sorted_textures.into_iter().map(|t| t.clone()).collect();
|
||||||
|
let sorted_paths: Vec<String> = 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!");
|
info!("Texture atlas created!");
|
||||||
//debug!(format!("{:?}", regions));
|
|
||||||
|
|
||||||
/*let t1 = Instant::now();
|
|
||||||
let delta = t1.duration_since(t0);
|
|
||||||
println!("{:?}", delta);*/
|
|
||||||
|
|
||||||
TextureAtlas {
|
TextureAtlas {
|
||||||
atlas: base,
|
atlas: base,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue