diff --git a/crates/bevy_gizmos/Cargo.toml b/crates/bevy_gizmos/Cargo.toml index dc8059f927f5a..9ec0283894875 100644 --- a/crates/bevy_gizmos/Cargo.toml +++ b/crates/bevy_gizmos/Cargo.toml @@ -22,6 +22,8 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.18.0-dev" } bevy_gizmos_macros = { path = "macros", version = "0.18.0-dev" } bevy_time = { path = "../bevy_time", version = "0.18.0-dev" } +bevy_text = { path = "../bevy_text", version = "0.18.0-dev" } +bevy_image = { path = "../bevy_image", version = "0.18.0-dev" } [lints] workspace = true diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index 34f9f476dcaf7..fbe6ca6548f56 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -7,7 +7,9 @@ use core::{ ops::{Deref, DerefMut}, }; +use bevy_asset::Assets; use bevy_color::{Color, LinearRgba}; +use bevy_ecs::system::ResMut; use bevy_ecs::{ change_detection::Tick, query::FilteredAccessSet, @@ -18,14 +20,18 @@ use bevy_ecs::{ }, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; +use bevy_image::{Image, TextureAtlasLayout}; use bevy_math::{bounding::Aabb3d, Isometry2d, Isometry3d, Vec2, Vec3}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; + +use bevy_text::{CosmicFontSystem, Font, FontAtlasSet, SwashCache, TextLayoutInfo}; use bevy_transform::TransformPoint; use bevy_utils::default; use crate::{ config::{DefaultGizmoConfigGroup, GizmoConfigGroup, GizmoConfigStore}, prelude::GizmoConfig, + text::{GizmoText, GizmoTextBuffer}, }; /// Storage of gizmo primitives. @@ -35,6 +41,9 @@ pub struct GizmoStorage { pub(crate) list_colors: Vec, pub(crate) strip_positions: Vec, pub(crate) strip_colors: Vec, + pub(crate) glyph_vertices: Vec, + pub(crate) glyph_uvs: Vec, + pub(crate) glyph_colors: Vec, marker: PhantomData<(Config, Clear)>, } @@ -45,6 +54,9 @@ impl Default for GizmoStorage { list_colors: default(), strip_positions: default(), strip_colors: default(), + glyph_vertices: default(), + glyph_uvs: default(), + glyph_colors: default(), marker: PhantomData, } } @@ -64,6 +76,12 @@ where self.list_colors.extend(other.list_colors.iter()); self.strip_positions.extend(other.strip_positions.iter()); self.strip_colors.extend(other.strip_colors.iter()); + + { + self.glyph_vertices.extend(other.glyph_vertices.iter()); + self.glyph_uvs.extend(other.glyph_uvs.iter()); + self.glyph_colors.extend(other.glyph_colors.iter()); + } } pub(crate) fn swap( @@ -74,6 +92,12 @@ where mem::swap(&mut self.list_colors, &mut other.list_colors); mem::swap(&mut self.strip_positions, &mut other.strip_positions); mem::swap(&mut self.strip_colors, &mut other.strip_colors); + + { + mem::swap(&mut self.glyph_vertices, &mut other.glyph_vertices); + mem::swap(&mut self.glyph_uvs, &mut other.glyph_uvs); + mem::swap(&mut self.glyph_colors, &mut other.glyph_colors); + } } /// Clear this gizmo storage of any requested gizmos. @@ -82,6 +106,12 @@ where self.list_colors.clear(); self.strip_positions.clear(); self.strip_colors.clear(); + + { + self.glyph_vertices.clear(); + self.glyph_uvs.clear(); + self.glyph_colors.clear() + } } } @@ -150,6 +180,30 @@ where pub config: &'w GizmoConfig, /// The currently used [`GizmoConfigGroup`] pub config_ext: &'w Config, + /// text buffer + pub text_buffer: ResMut<'w, GizmoTextBuffer>, +} + +impl<'w, 's, Config, Clear> Gizmos<'w, 's, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + /// Draw text + pub fn text_2d( + &mut self, + position: Vec2, + text: impl Into, + size: f32, + color: impl Into, + ) { + self.text_buffer.text.push(GizmoText { + position, + text: text.into(), + size, + color: color.into(), + }); + } } impl<'w, 's, Config, Clear> Deref for Gizmos<'w, 's, Config, Clear> @@ -177,7 +231,9 @@ where type GizmosState = ( Deferred<'static, GizmoBuffer>, Res<'static, GizmoConfigStore>, + ResMut<'static, GizmoTextBuffer>, ); + #[doc(hidden)] pub struct GizmosFetchState where @@ -237,6 +293,7 @@ where } #[inline] + unsafe fn get_param<'w, 's>( state: &'s mut Self::State, system_meta: &SystemMeta, @@ -244,7 +301,7 @@ where change_tick: Tick, ) -> Self::Item<'w, 's> { // SAFETY: Delegated to existing `SystemParam` implementations. - let (mut f0, f1) = unsafe { + let (mut f0, f1, text_buffer) = unsafe { GizmosState::::get_param( &mut state.state, system_meta, @@ -263,6 +320,7 @@ where buffer: f0, config, config_ext, + text_buffer, } } } @@ -298,6 +356,12 @@ where pub strip_positions: Vec, /// The colors of line strip vertices. pub strip_colors: Vec, + /// The positions the glyph vertices. + pub glyph_vertices: Vec, + /// The UV coords for the glyph vertices. + pub glyph_uvs: Vec, + /// The colors for each glyph vertex. + pub glyph_colors: Vec, #[reflect(ignore, clone)] pub(crate) marker: PhantomData<(Config, Clear)>, } @@ -314,6 +378,12 @@ where list_colors: Vec::new(), strip_positions: Vec::new(), strip_colors: Vec::new(), + + glyph_vertices: Vec::new(), + + glyph_uvs: Vec::new(), + + glyph_colors: Vec::new(), marker: PhantomData, } } @@ -329,6 +399,15 @@ pub struct GizmoBufferView<'a> { pub strip_positions: &'a Vec, /// Vertex colors for line-strip topology. pub strip_colors: &'a Vec, + + /// Vertex positions for glyphs. + pub glyph_vertices: &'a Vec, + + /// Vertex UVs for glyphs. + pub glyph_uvs: &'a Vec, + + /// Vertex colors for glyphs. + pub glyph_colors: &'a Vec, } impl SystemBuffer for GizmoBuffer @@ -365,6 +444,12 @@ where list_colors, strip_positions, strip_colors, + + glyph_vertices, + + glyph_uvs, + + glyph_colors, .. } = self; GizmoBufferView { @@ -372,6 +457,12 @@ where list_colors, strip_positions, strip_colors, + + glyph_vertices, + + glyph_uvs, + + glyph_colors, } } /// Draw a line in 3D from `start` to `end`. @@ -804,6 +895,37 @@ where self.line_gradient_2d(start, start + vector, start_color, end_color); } + /// Draw a glyph + #[inline] + pub fn draw_glyph_2d( + &mut self, + min: Vec2, + max: Vec2, + uv_min: Vec2, + uv_max: Vec2, + color: impl Into, + ) { + let linear_color = LinearRgba::from(color.into()); + + self.glyph_vertices.extend([ + min.extend(0.), + max.extend(0.), + Vec3::new(min.x, max.y, 0.), + min.extend(0.), + Vec3::new(max.x, min.y, 0.), + max.extend(0.), + ]); + self.glyph_uvs.extend([ + uv_min, + uv_max, + Vec2::new(uv_min.x, uv_max.y), + uv_min, + Vec2::new(uv_max.x, uv_min.y), + uv_max, + ]); + self.glyph_colors.extend(iter::repeat(linear_color).take(6)); + } + /// Draw a wireframe rectangle in 2D with the given `isometry` applied. /// /// If `isometry == Isometry2d::IDENTITY` then diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 9d6f6500f1476..59a6a74ed848b 100755 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -34,6 +34,7 @@ pub mod grid; pub mod primitives; pub mod retained; pub mod rounded_box; +pub mod text; #[cfg(feature = "bevy_light")] pub mod light; @@ -62,7 +63,7 @@ pub mod prelude { pub use crate::light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo}; } -use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, RunFixedMainLoop}; +use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, PostUpdate, RunFixedMainLoop}; use bevy_asset::{Asset, AssetApp, Assets, Handle}; use bevy_ecs::{ resource::Resource, @@ -71,7 +72,11 @@ use bevy_ecs::{ }; use bevy_reflect::TypePath; -use crate::{config::ErasedGizmoConfigGroup, gizmos::GizmoBuffer}; +use crate::{ + config::ErasedGizmoConfigGroup, + gizmos::GizmoBuffer, + text::{gizmo_text_system, GizmoText, GizmoTextBuffer}, +}; use bevy_time::Fixed; use bevy_utils::TypeIdMap; @@ -136,6 +141,7 @@ impl AppGizmoBuilder for App { self.init_resource::>() .init_resource::>() .init_resource::>>() + .init_resource::>() .add_systems( RunFixedMainLoop, start_gizmo_context:: @@ -148,6 +154,7 @@ impl AppGizmoBuilder for App { end_gizmo_context:: .in_set(bevy_app::RunFixedMainLoopSystems::AfterFixedMainLoop), ) + .add_systems(PostUpdate, gizmo_text_system::) .add_systems( Last, ( @@ -287,6 +294,9 @@ fn update_gizmo_meshes( list_colors: mem::take(&mut storage.list_colors), strip_positions: mem::take(&mut storage.strip_positions), strip_colors: mem::take(&mut storage.strip_colors), + glyph_vertices: mem::take(&mut storage.glyph_vertices), + glyph_uvs: mem::take(&mut storage.glyph_uvs), + glyph_colors: mem::take(&mut storage.glyph_colors), marker: PhantomData, }, }; diff --git a/crates/bevy_gizmos/src/text.rs b/crates/bevy_gizmos/src/text.rs new file mode 100644 index 0000000000000..59ea74603a3f5 --- /dev/null +++ b/crates/bevy_gizmos/src/text.rs @@ -0,0 +1,103 @@ +//! Text Gizmo functions + +use std::marker::PhantomData; + +use crate::config::GizmoConfigGroup; +use crate::gizmos::Gizmos; +use crate::prelude::Gizmo; +use bevy_asset::Assets; +use bevy_color::Color; +use bevy_ecs::entity::Entity; +use bevy_ecs::resource::Resource; +use bevy_ecs::schedule::CompactNodeIdAndDirection; +use bevy_ecs::system::{Res, ResMut}; +use bevy_image::{Image, TextureAtlasLayout}; +use bevy_math::{Isometry2d, Vec2}; +use bevy_text::{ + ComputedTextBlock, CosmicFontSystem, Font, FontAtlasSet, LineHeight, SwashCache, TextBounds, + TextFont, TextLayout, TextLayoutInfo, TextPipeline, +}; +use bevy_utils::default; + +#[derive(Resource, Default)] +pub struct GizmoTextBuffer +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + pub text: Vec, + phantom: PhantomData<(Config, Clear)>, +} + +pub struct GizmoText { + pub position: Vec2, + pub text: String, + pub size: f32, + pub color: Color, +} + +pub fn gizmo_text_system( + mut textures: ResMut>, + fonts: Res>, + mut text_pipeline: ResMut, + mut texture_atlases: ResMut>, + mut font_atlas_set: ResMut, + mut font_system: ResMut, + mut swash_cache: ResMut, + mut gizmos: Gizmos, +) where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + let texts = core::mem::take(&mut gizmos.text_buffer.text); + let mut info = TextLayoutInfo::default(); + for text in texts.iter() { + let mut block = ComputedTextBlock::default(); + let span = ( + Entity::PLACEHOLDER, + 0, + text.text.as_str(), + &TextFont { + font_size: text.size, + ..default() + }, + text.color, + LineHeight::default(), + ); + + let Ok(()) = text_pipeline.queue_text( + &mut info, + &fonts, + core::iter::once(span), + 1., + &TextLayout::new_with_no_wrap(), + TextBounds::UNBOUNDED, + &mut font_atlas_set, + &mut texture_atlases, + &mut textures, + &mut block, + &mut font_system, + &mut swash_cache, + ) else { + continue; + }; + + println!("result: {}", info.glyphs.len()); + + for glyph in info.glyphs.iter() { + let rect = texture_atlases + .get(glyph.atlas_info.texture_atlas) + .unwrap() + .textures[glyph.atlas_info.location.glyph_index] + .as_rect(); + let position = glyph.position + text.position; + gizmos.draw_glyph_2d( + position, + position + glyph.size, + rect.min, + rect.max, + text.color, + ); + } + } +} diff --git a/crates/bevy_gizmos_render/src/lib.rs b/crates/bevy_gizmos_render/src/lib.rs index 42e73e9192c00..78feec266c1d9 100755 --- a/crates/bevy_gizmos_render/src/lib.rs +++ b/crates/bevy_gizmos_render/src/lib.rs @@ -23,6 +23,7 @@ pub mod retained; mod pipeline_2d; #[cfg(feature = "bevy_pbr")] mod pipeline_3d; +pub mod text; use bevy_app::{App, Plugin}; use bevy_ecs::{ diff --git a/crates/bevy_gizmos_render/src/text.rs b/crates/bevy_gizmos_render/src/text.rs new file mode 100644 index 0000000000000..9784dc0f617d3 --- /dev/null +++ b/crates/bevy_gizmos_render/src/text.rs @@ -0,0 +1,12 @@ +use bevy_asset::Handle; +use bevy_ecs::resource::Resource; +use bevy_render::render_resource::BindGroupLayoutDescriptor; +use bevy_shader::Shader; +use bevy_sprite_render::Mesh2dPipeline; + +#[derive(Clone, Resource)] +struct TextGizmoPipeline { + mesh_pipeline: Mesh2dPipeline, + uniform_layout: BindGroupLayoutDescriptor, + shader: Handle, +} diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs index 0db7e6ac55a7e..889f77fb70a65 100644 --- a/examples/gizmos/2d_gizmos.rs +++ b/examples/gizmos/2d_gizmos.rs @@ -38,11 +38,11 @@ fn setup(mut commands: Commands) { } fn draw_example_collection( - mut gizmos: Gizmos, - mut my_gizmos: Gizmos, + mut gizmos_set: ParamSet<(Gizmos, Gizmos)>, time: Res