Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get example working with meshtext #22

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = { version = "0.3.0", features = ["owned"] }

[dependencies.bevy]
version = "0.12.0"
Expand All @@ -35,6 +35,7 @@ features = [
"bevy_sprite",
"x11",
"tonemapping_luts",
"webgl2"
]

[features]
Expand Down
7 changes: 3 additions & 4 deletions examples/3d_scene.rs
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -31,6 +29,7 @@ fn setup_text_mesh(mut commands: Commands, asset_server: Res<AssetServer>) {
},
transform: Transform {
translation: Vec3::new(-1., 1.75, 0.),
scale: Vec3::new(0.5, 0.5, 0.1),
..Default::default()
},
..Default::default()
Expand All @@ -44,7 +43,7 @@ fn setup_text_mesh(mut commands: Commands, asset_server: Res<AssetServer>) {
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()
Expand Down
5 changes: 4 additions & 1 deletion examples/performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
23 changes: 19 additions & 4 deletions src/font_loader.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -11,6 +12,12 @@ use bevy::reflect::{TypePath, TypeUuid};
#[derive(Debug)]
pub struct FontLoaderError;

impl From<InvalidFont> for FontLoaderError {
fn from(_value: InvalidFont) -> Self {
Self
}
}

impl Error for FontLoaderError {}

impl Display for FontLoaderError {
Expand All @@ -30,7 +37,7 @@ 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<Self::Asset, Self::Error>> {
Box::pin(async move {
Expand All @@ -39,11 +46,19 @@ impl AssetLoader for FontLoader {
.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.clone());
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);
Expand All @@ -62,7 +77,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<meshtext::OwnedFace>,
}

impl std::fmt::Debug for TextMeshFont {
Expand Down
3 changes: 2 additions & 1 deletion src/mesh_cache.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -42,7 +43,7 @@ impl Eq for Depth {}

#[derive(Resource)]
pub struct MeshCache {
pub(crate) meshes: HashMap<CacheKey, ttf2mesh::Mesh<'static, ttf2mesh::Mesh3d>>,
pub(crate) meshes: HashMap<CacheKey, IndexedMeshText>,
}

impl Default for MeshCache {
Expand Down
144 changes: 19 additions & 125 deletions src/mesh_data_generator.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy::prelude::*;
use ttf2mesh::{TTFFile, Value};
use meshtext::{Glyph, MeshGenerator, OwnedFace};

use crate::{
mesh_cache::{CacheKey, MeshCache},
Expand All @@ -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<OwnedFace>,
cache: Option<&mut MeshCache>,
) -> MeshData {
trace!("Generate text mesh: {:?}", text_mesh.text);
Expand All @@ -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;
Expand All @@ -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::<Vec<_>>();

Expand Down Expand Up @@ -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();
let mut font = MeshGenerator::new(bytes);

let text_mesh = TextMesh {
text: "hello world!".to_string(),
Expand Down
7 changes: 4 additions & 3 deletions src/mesh_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
6 changes: 3 additions & 3 deletions src/text_mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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(),
}
}
}
Expand Down