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

3d text widget #7426

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
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
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,16 @@ description = "A scene showcasing the built-in 3D shapes"
category = "3D Rendering"
wasm = true

[[example]]
name = "3d_text"
path = "examples/3d/3d_text.rs"

[package.metadata.example.3d_text]
name = "3D text"
description = "A scene showcasing text in 3D"
category = "3D Rendering"
wasm = true

[[example]]
name = "atmospheric_fog"
path = "examples/3d/atmospheric_fog.rs"
Expand Down
6 changes: 4 additions & 2 deletions crates/bevy_text/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@ subpixel_glyph_atlas = []
bevy_app = { path = "../bevy_app", version = "0.9.0" }
bevy_asset = { path = "../bevy_asset", version = "0.9.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.9.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.9.0" }
bevy_math = { path = "../bevy_math", version = "0.9.0" }
bevy_pbr = { path = "../bevy_pbr", version = "0.9.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.9.0", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.9.0" }
bevy_sprite = { path = "../bevy_sprite", version = "0.9.0" }
bevy_transform = { path = "../bevy_transform", version = "0.9.0" }
bevy_window = { path = "../bevy_window", version = "0.9.0" }
bevy_utils = { path = "../bevy_utils", version = "0.9.0" }
bevy_window = { path = "../bevy_window", version = "0.9.0" }

# other
anyhow = "1.0.4"
ab_glyph = "0.2.6"
glyph_brush_layout = "0.2.1"
thiserror = "1.0"
serde = {version = "1", features = ["derive"]}
serde = { version = "1", features = ["derive"] }
25 changes: 23 additions & 2 deletions crates/bevy_text/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod glyph_brush;
mod pipeline;
mod text;
mod text2d;
mod text3d;
mod text_layout;

pub use error::*;
pub use font::*;
Expand All @@ -17,10 +19,14 @@ pub use glyph_brush::*;
pub use pipeline::*;
pub use text::*;
pub use text2d::*;
pub use text3d::*;
pub use text_layout::*;

pub mod prelude {
#[doc(hidden)]
pub use crate::{Font, Text, Text2dBundle, TextAlignment, TextError, TextSection, TextStyle};
pub use crate::{
Font, Text, Text2dBundle, Text3dBundle, TextAlignment, TextError, TextSection, TextStyle,
};
}

use bevy_app::prelude::*;
Expand Down Expand Up @@ -81,14 +87,29 @@ impl Plugin for TextPlugin {
.init_resource::<FontAtlasWarning>()
.insert_resource(TextPipeline::default())
.add_system(
update_text2d_layout
text_layout::update_text_layout::<Text2dBounds>
.in_base_set(CoreSet::PostUpdate)
.after(ModifiesWindows)
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
// will only ever observe its own render target, and `update_text2d_layout`
// will never modify a pre-existing `Image` asset.
.ambiguous_with(CameraUpdateSystem),
)
.add_system(
text_layout::update_text_layout::<Text3dBounds>
.in_base_set(CoreSet::PostUpdate)
.after(ModifiesWindows)
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
// will only ever observe its own render target, and `update_text3d_layout`
// will never modify a pre-existing `Image` asset.
.ambiguous_with(CameraUpdateSystem),
)
.add_system(
text3d::update_text3d_mesh
.in_base_set(CoreSet::PostUpdate)
.after(text_layout::update_text_layout::<Text3dBounds>),
);

if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
Expand Down
115 changes: 15 additions & 100 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
use crate::{Text, TextLayoutInfo};
use bevy_asset::Assets;
use bevy_ecs::{
bundle::Bundle,
change_detection::{DetectChanges, Ref},
component::Component,
entity::Entity,
event::EventReader,
prelude::With,
reflect::ReflectComponent,
system::{Commands, Local, Query, Res, ResMut},
system::{Query, Res, ResMut},
};
use bevy_math::{Vec2, Vec3};
use bevy_reflect::Reflect;
use bevy_render::{
prelude::Color,
texture::Image,
view::{ComputedVisibility, Visibility},
Extract,
};
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas};
use bevy_transform::prelude::{GlobalTransform, Transform};
use bevy_utils::HashSet;
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};

use crate::{
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
TextSettings, YAxisOrientation,
};
use bevy_window::{PrimaryWindow, Window};

/// The maximum width and height of text. The text will wrap according to the specified size.
/// Characters out of the bounds after wrapping will be truncated. Text is aligned according to the
Expand Down Expand Up @@ -67,19 +59,23 @@ pub struct Text2dBundle {
pub computed_visibility: ComputedVisibility,
}

#[allow(clippy::type_complexity)]
pub fn extract_text2d_sprite(
mut extracted_sprites: ResMut<ExtractedSprites>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
text2d_query: Extract<
Query<(
Entity,
&ComputedVisibility,
&Text,
&TextLayoutInfo,
&Anchor,
&GlobalTransform,
)>,
Query<
(
Entity,
&ComputedVisibility,
&Text,
&TextLayoutInfo,
&Anchor,
&GlobalTransform,
),
With<Text2dBounds>,
>,
>,
) {
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
Expand Down Expand Up @@ -136,84 +132,3 @@ pub fn extract_text2d_sprite(
}
}
}

/// Updates the layout and size information whenever the text or style is changed.
/// This information is computed by the `TextPipeline` on insertion, then stored.
///
/// ## World Resources
///
/// [`ResMut<Assets<Image>>`](Assets<Image>) -- This system only adds new [`Image`] assets.
/// It does not modify or observe existing ones.
#[allow(clippy::too_many_arguments)]
pub fn update_text2d_layout(
mut commands: Commands,
// Text items which should be reprocessed again, generally when the font hasn't loaded yet.
mut queue: Local<HashSet<Entity>>,
mut textures: ResMut<Assets<Image>>,
fonts: Res<Assets<Font>>,
text_settings: Res<TextSettings>,
mut font_atlas_warning: ResMut<FontAtlasWarning>,
windows: Query<&Window, With<PrimaryWindow>>,
mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
mut text_pipeline: ResMut<TextPipeline>,
mut text_query: Query<(
Entity,
Ref<Text>,
&Text2dBounds,
Option<&mut TextLayoutInfo>,
)>,
) {
// We need to consume the entire iterator, hence `last`
let factor_changed = scale_factor_changed.iter().last().is_some();

// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
let scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.0);

for (entity, text, bounds, text_layout_info) in &mut text_query {
if factor_changed || text.is_changed() || queue.remove(&entity) {
let text_bounds = Vec2::new(
scale_value(bounds.size.x, scale_factor),
scale_value(bounds.size.y, scale_factor),
);

match text_pipeline.queue_text(
&fonts,
&text.sections,
scale_factor,
text.alignment,
text.linebreak_behaviour,
text_bounds,
&mut font_atlas_set_storage,
&mut texture_atlases,
&mut textures,
text_settings.as_ref(),
&mut font_atlas_warning,
YAxisOrientation::BottomToTop,
) {
Err(TextError::NoSuchFont) => {
// There was an error processing the text layout, let's add this entity to the
// queue for further processing
queue.insert(entity);
}
Err(e @ TextError::FailedToAddGlyph(_)) => {
panic!("Fatal error when processing text: {e}.");
}
Ok(info) => match text_layout_info {
Some(mut t) => *t = info,
None => {
commands.entity(entity).insert(info);
}
},
}
}
}
}

pub fn scale_value(value: f32, factor: f64) -> f32 {
(value as f64 * factor) as f32
}
Loading