From 729b5644f218302a8ab049819ba7556e73b6b012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20=C3=96dmark?= Date: Fri, 16 Dec 2022 14:41:11 +0100 Subject: [PATCH 1/3] Get example working with meshtext --- Cargo.toml | 2 +- examples/3d_scene.rs | 7 +- examples/performance.rs | 5 +- src/font_loader.rs | 14 +++- src/mesh_cache.rs | 3 +- src/mesh_data_generator.rs | 144 +++++-------------------------------- src/mesh_system.rs | 7 +- src/text_mesh.rs | 6 +- 8 files changed, 47 insertions(+), 141 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01f6651..b5364f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,10 @@ license = "MIT" readme = "README.md" [dependencies] -ttf2mesh = "0.2.0" bitflags = "2.1" anyhow = "1.0" glyph_brush_layout = "0.2.3" +meshtext = "0.2.1" [dependencies.bevy] version = "0.12.0" diff --git a/examples/3d_scene.rs b/examples/3d_scene.rs index 903baae..6b07373 100644 --- a/examples/3d_scene.rs +++ b/examples/3d_scene.rs @@ -1,8 +1,6 @@ -use std::time::Duration; - use bevy::{prelude::*, render::camera::Camera}; use bevy_text_mesh::prelude::*; - +use std::time::Duration; fn main() { App::new() .insert_resource(Msaa::Sample4) @@ -31,6 +29,7 @@ fn setup_text_mesh(mut commands: Commands, asset_server: Res) { }, transform: Transform { translation: Vec3::new(-1., 1.75, 0.), + scale: Vec3::new(0.5, 0.5, 0.1), ..Default::default() }, ..Default::default() @@ -44,7 +43,7 @@ fn setup_text_mesh(mut commands: Commands, asset_server: Res) { font: font.clone(), font_size: SizeUnit::NonStandard(36.), color: Color::rgb(0.0, 1.0, 0.0), - mesh_quality: Quality::Custom(128), + mesh_quality: QualitySettings::default(), ..Default::default() }, ..Default::default() diff --git a/examples/performance.rs b/examples/performance.rs index ef956b3..70cff30 100644 --- a/examples/performance.rs +++ b/examples/performance.rs @@ -15,7 +15,10 @@ use rand::prelude::*; // NOTE! Custom (unlit) material used // tessellation quality -const MESH_QUALITY: Quality = Quality::Low; +const MESH_QUALITY: QualitySettings = QualitySettings { + quad_interpolation_steps: 3, + cubic_interpolation_steps: 1, +}; // how often new texts are spawned const TEXT_SPAWN_INTERVAL: u64 = 125; diff --git a/src/font_loader.rs b/src/font_loader.rs index 567b1e4..84e245d 100644 --- a/src/font_loader.rs +++ b/src/font_loader.rs @@ -33,6 +33,9 @@ impl AssetLoader for FontLoader { _: &'a Self::Settings, load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result> { + let bytes: Vec = bytes.into(); + let bytes = bytes.leak(); + Box::pin(async move { let mut bytes = Vec::new(); reader @@ -40,10 +43,15 @@ impl AssetLoader for FontLoader { .await .expect("unable to read font"); + let common = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!.,".to_string(); + + let mut generator = meshtext::MeshGenerator::new(&bytes); + generator.precache_glyphs(&common, false, None).unwrap(); + // ttf fontloading let font = TextMeshFont { - ttf_font: ttf2mesh::TTFFile::from_buffer_vec(bytes.clone()) - .expect("unable to decode asset"), + ttf_font_generator: generator, }; load_context.add_labeled_asset("mesh".into(), font); @@ -62,7 +70,7 @@ impl AssetLoader for FontLoader { #[derive(TypeUuid, TypePath, Asset)] #[uuid = "5415ac03-d009-471e-89ab-dc0d4e31a8c4"] pub struct TextMeshFont { - pub(crate) ttf_font: ttf2mesh::TTFFile, + pub(crate) ttf_font_generator: meshtext::MeshGenerator, } impl std::fmt::Debug for TextMeshFont { diff --git a/src/mesh_cache.rs b/src/mesh_cache.rs index de22a27..1b29c60 100644 --- a/src/mesh_cache.rs +++ b/src/mesh_cache.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, hash::Hash}; use bevy::prelude::*; +use meshtext::IndexedMeshText; // TODO: add accuracy to depth cache // TODO: purge cached entries, keep count per depth, and if it reaches zero @@ -42,7 +43,7 @@ impl Eq for Depth {} #[derive(Resource)] pub struct MeshCache { - pub(crate) meshes: HashMap>, + pub(crate) meshes: HashMap, } impl Default for MeshCache { diff --git a/src/mesh_data_generator.rs b/src/mesh_data_generator.rs index 5b67d40..506b778 100644 --- a/src/mesh_data_generator.rs +++ b/src/mesh_data_generator.rs @@ -1,5 +1,5 @@ use bevy::prelude::*; -use ttf2mesh::{TTFFile, Value}; +use meshtext::{Glyph, MeshGenerator}; use crate::{ mesh_cache::{CacheKey, MeshCache}, @@ -20,7 +20,7 @@ pub(crate) struct MeshData { // from the existing mesh pub(crate) fn generate_text_mesh( text_mesh: &TextMesh, - font: &mut TTFFile, + font: &mut MeshGenerator, cache: Option<&mut MeshCache>, ) -> MeshData { trace!("Generate text mesh: {:?}", text_mesh.text); @@ -35,11 +35,6 @@ pub(crate) fn generate_text_mesh( } }; - // TODO performance: pre-allocate capacity - let mut vertices = Vec::new(); //with_capacity(4308); // TODO: allocate opportunistically - let mut normals = Vec::new(); //with_capacity(4308); // TODO: allocate opportunistically - let mut indices = Vec::new(); //with_capacity(8520); - let mut vertices_offset: usize = 0; let depth = 0.08; @@ -57,126 +52,24 @@ pub(crate) fn generate_text_mesh( None => todo!("Font automatic sizing has not been implemented yet"), }; - let spacing = Vec2::new(0.08, 0.1) * scalar; - let mut scaled_offset = Vec2::ZERO; let mut scaled_row_y_max_height = 0.; - //println!("scalar={}, spacing={}", scalar, spacing); - for char in text.chars() { - //println!("{} offset={}", char, scaled_offset); - if char == ' ' { - scaled_offset.x += 0.2 * scalar + spacing.x; - continue; - } else if char == '\n' { - scaled_offset.x = 0.; - scaled_offset.y -= scaled_row_y_max_height + spacing.y; - continue; - } - - let key = CacheKey::new_3d(char, depth); - - let mesh = match cache.meshes.get(&key) { - Some(mesh) => mesh, - None => { - let glyph = font.glyph_from_char(char); - - let mut glyph = match glyph { - Ok(glyph) => glyph, - Err(_) => { - println!("Glyph {} not found", char); - font.glyph_from_char('?').unwrap() - } - }; - - let mesh = match &text_mesh.size.depth { - Some(unit) => glyph - .to_3d_mesh(text_mesh.style.mesh_quality, unit.as_scalar().unwrap()) - .unwrap(), - None => todo!("2d glyphs are not implemented yet. Define depth"), - }; - - cache.meshes.insert(key.clone(), mesh); - cache.meshes.get(&key).unwrap() - } - }; - - let (mut xmin, mut xmax) = (f32::MAX, f32::MIN); - let (mut ymin, mut ymax) = (f32::MAX, f32::MIN); - for vertex in mesh.iter_vertices() { - let (x, y, _z) = vertex.val(); - // optimization possibility: calculate per-glyph min/max when caching - if x < xmin { - xmin = x; - } - if x > xmax { - xmax = x; - } - - if y < ymin { - ymin = y; - } - if y > ymax { - ymax = y; - } - } - - let y_diff = (ymax - ymin) * scalar; - if scaled_row_y_max_height < y_diff { - scaled_row_y_max_height = y_diff; - } - - for vertex in mesh.iter_vertices() { - let (x, y, z) = vertex.val(); - vertices.push([ - x * scalar + scaled_offset.x - xmin * scalar, - y * scalar + scaled_offset.y, - z * scalar, - ]); - } - - /* - println!( - " - x({:.3} - {:.3})={:.3}, y({:.3} - {:.3})={:.3}", - xmin * scalar, - xmax * scalar, - (xmax - xmin) * scalar, - ymin * scalar, - ymax * scalar, - (ymax - ymin) * scalar - ); - */ - // 13 microsecs - - for normal in mesh.iter_normals().unwrap() { - let (x, y, z) = normal.val(); - normals.push([x, y, z]); - } - // total = 24ms - - for face in mesh.iter_faces() { - let val = face.val(); - indices.extend_from_slice(&[ - (val.0) as u32 + vertices_offset as u32, - (val.1) as u32 + vertices_offset as u32, - (val.2) as u32 + vertices_offset as u32, - ]); - } - // 30 microsecs - - vertices_offset += mesh.vertices_len(); - - scaled_offset.x += (xmax - xmin) * scalar + spacing.x; - - if text_mesh.size.wrapping - && scaled_offset.x + scalar + spacing.x > text_mesh.size.width.as_scalar().unwrap() - { - scaled_offset.x = 0.; - scaled_offset.y -= scaled_row_y_max_height + spacing.y; - } - - //println!(""); - } + //TODO: use the input in text_mesh to generate the Mat4 vector to be used for transformation when generating the text. + use meshtext::TextSection; + let sectionmesh: meshtext::IndexedMeshText = font.generate_section(&text, false, None).unwrap(); + + let indices = sectionmesh.indices.clone(); + let vertices: Vec<[f32; 3]> = sectionmesh + .vertices + .chunks(3) + .map(|chunk| [chunk[0], chunk[1], chunk[2]]) + .collect(); + let normals: Vec<[f32; 3]> = sectionmesh + .vertices + .chunks(3) + .map(|chunk| [chunk[0], chunk[1], chunk[2]]) + .collect(); let uvs = vertices.iter().map(|_vert| [0., 1.]).collect::>(); @@ -204,7 +97,8 @@ mod tests { #[test] fn test_generate_mesh() { let mut mesh_cache = MeshCache::default(); - let mut font = ttf2mesh::TTFFile::from_buffer_vec(get_font_bytes()).unwrap(); + let bytes = get_font_bytes().leak(); + let mut font = MeshGenerator::new(bytes); let text_mesh = TextMesh { text: "hello world!".to_string(), diff --git a/src/mesh_system.rs b/src/mesh_system.rs index cf49da7..18a83b0 100644 --- a/src/mesh_system.rs +++ b/src/mesh_system.rs @@ -57,19 +57,20 @@ pub(crate) fn text_mesh( } }; - let ttf2_mesh = generate_text_mesh(&text_mesh, &mut font.ttf_font, Some(&mut cache)); + let meshtext = + generate_text_mesh(&text_mesh, &mut font.ttf_font_generator, Some(&mut cache)); match mesh { Some(mesh) => { let mesh = meshes.get_mut(mesh).unwrap(); - apply_mesh(ttf2_mesh, mesh); + apply_mesh(meshtext, mesh); // TODO: handle color updates } None => { let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); - apply_mesh(ttf2_mesh, &mut mesh); + apply_mesh(meshtext, &mut mesh); commands.entity(entity).insert(PbrBundle { mesh: meshes.add(mesh), diff --git a/src/text_mesh.rs b/src/text_mesh.rs index cb5d52e..c23dbac 100644 --- a/src/text_mesh.rs +++ b/src/text_mesh.rs @@ -2,7 +2,7 @@ use bevy::prelude::*; use glyph_brush_layout::{HorizontalAlign, VerticalAlign}; use crate::{mesh_system::TextMeshState, TextMeshFont}; -pub use ttf2mesh::Quality; +pub use meshtext::QualitySettings; #[derive(Default, Bundle, Debug)] pub struct TextMeshBundle { @@ -108,7 +108,7 @@ pub struct TextMeshStyle { pub font_size: SizeUnit, pub font_style: FontStyle, pub color: Color, - pub mesh_quality: Quality, + pub mesh_quality: QualitySettings, } impl Default for TextMeshStyle { @@ -118,7 +118,7 @@ impl Default for TextMeshStyle { font_size: SizeUnit::NonStandard(DEFAULT_FONT_SIZE), font_style: FontStyle::default(), color: Color::WHITE, - mesh_quality: Quality::Medium, + mesh_quality: QualitySettings::default(), } } } From e9c4afae42b86b8bb2f5583f0d0bf9eda08ff229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20Sch=C3=A4ffler?= Date: Sat, 7 Jan 2023 13:04:24 +0100 Subject: [PATCH 2/3] Use owned feature of meshtext to prevent leaking the font data. --- Cargo.toml | 2 +- src/font_loader.rs | 19 +++++++++++++------ src/mesh_data_generator.rs | 6 +++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b5364f6..9ff86ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" bitflags = "2.1" anyhow = "1.0" glyph_brush_layout = "0.2.3" -meshtext = "0.2.1" +meshtext = { version = "0.3.0", features = ["owned"] } [dependencies.bevy] version = "0.12.0" diff --git a/src/font_loader.rs b/src/font_loader.rs index 84e245d..e666b62 100644 --- a/src/font_loader.rs +++ b/src/font_loader.rs @@ -1,5 +1,6 @@ use bevy::asset::AsyncReadExt; use bevy::text::Font; +use glyph_brush_layout::ab_glyph::InvalidFont; use std::error::Error; use std::fmt::Display; @@ -11,6 +12,12 @@ use bevy::reflect::{TypePath, TypeUuid}; #[derive(Debug)] pub struct FontLoaderError; +impl From for FontLoaderError { + fn from(_value: InvalidFont) -> Self { + Self + } +} + impl Error for FontLoaderError {} impl Display for FontLoaderError { @@ -30,23 +37,23 @@ impl AssetLoader for FontLoader { fn load<'a>( &'a self, reader: &'a mut Reader, - _: &'a Self::Settings, + _s: &'a Self::Settings, load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result> { - let bytes: Vec = bytes.into(); - let bytes = bytes.leak(); - Box::pin(async move { let mut bytes = Vec::new(); reader .read_to_end(&mut bytes) .await .expect("unable to read font"); + // standard bevy_text/src/font_loader code + let font = Font::try_from_bytes(bytes.clone())?; + load_context.add_labeled_asset("font".into(), font); let common = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!.,".to_string(); - let mut generator = meshtext::MeshGenerator::new(&bytes); + let mut generator = meshtext::MeshGenerator::new(bytes.clone()); generator.precache_glyphs(&common, false, None).unwrap(); // ttf fontloading @@ -70,7 +77,7 @@ impl AssetLoader for FontLoader { #[derive(TypeUuid, TypePath, Asset)] #[uuid = "5415ac03-d009-471e-89ab-dc0d4e31a8c4"] pub struct TextMeshFont { - pub(crate) ttf_font_generator: meshtext::MeshGenerator, + pub(crate) ttf_font_generator: meshtext::MeshGenerator, } impl std::fmt::Debug for TextMeshFont { diff --git a/src/mesh_data_generator.rs b/src/mesh_data_generator.rs index 506b778..d671764 100644 --- a/src/mesh_data_generator.rs +++ b/src/mesh_data_generator.rs @@ -1,5 +1,5 @@ use bevy::prelude::*; -use meshtext::{Glyph, MeshGenerator}; +use meshtext::{Glyph, MeshGenerator, OwnedFace}; use crate::{ mesh_cache::{CacheKey, MeshCache}, @@ -20,7 +20,7 @@ pub(crate) struct MeshData { // from the existing mesh pub(crate) fn generate_text_mesh( text_mesh: &TextMesh, - font: &mut MeshGenerator, + font: &mut MeshGenerator, cache: Option<&mut MeshCache>, ) -> MeshData { trace!("Generate text mesh: {:?}", text_mesh.text); @@ -97,7 +97,7 @@ mod tests { #[test] fn test_generate_mesh() { let mut mesh_cache = MeshCache::default(); - let bytes = get_font_bytes().leak(); + let bytes = get_font_bytes(); let mut font = MeshGenerator::new(bytes); let text_mesh = TextMesh { From 634db94f6b98d7026b2c6389a0f75ec9ee4b7eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20=C3=96dmark?= Date: Wed, 27 Dec 2023 11:46:20 +0100 Subject: [PATCH 3/3] webgl2 feature for dev --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 9ff86ed..785e2bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ features = [ "bevy_sprite", "x11", "tonemapping_luts", + "webgl2" ] [features]