From 3065c95e38f668e20c51e36b71c320e1756667dc Mon Sep 17 00:00:00 2001 From: Schmarni Date: Mon, 16 Dec 2024 00:15:00 +0100 Subject: [PATCH] refactor: absolutly minimal text impl, doesn't yet care about state change or despawn Signed-off-by: Schmarni --- src/bevy_plugin.rs | 10 + src/nodes/drawable/lines.rs | 38 ++-- src/nodes/drawable/text.rs | 351 +++++++++++++++++++++++++----------- 3 files changed, 272 insertions(+), 127 deletions(-) diff --git a/src/bevy_plugin.rs b/src/bevy_plugin.rs index 668ac3b..c38808a 100644 --- a/src/bevy_plugin.rs +++ b/src/bevy_plugin.rs @@ -5,6 +5,7 @@ use bevy::{ use bevy_mod_openxr::session::OxrSession; use bevy_mod_xr::session::{session_available, XrSessionCreated}; use openxr::ReferenceSpaceType; +use stardust_xr::values::color::{color_space::LinearRgb, AlphaColor, Rgb}; use crate::objects::Inputs; @@ -89,6 +90,15 @@ impl StardustAabb3dExt for Aabb3d { } } +pub const fn convert_linear_rgba(c: AlphaColor>) -> LinearRgba { + LinearRgba { + red: c.c.r, + green: c.c.g, + blue: c.c.b, + alpha: c.a, + } +} + #[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)] pub struct StardustExtract; #[derive(Component, Hash, Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/nodes/drawable/lines.rs b/src/nodes/drawable/lines.rs index b15b44b..4d3fa77 100644 --- a/src/nodes/drawable/lines.rs +++ b/src/nodes/drawable/lines.rs @@ -6,18 +6,13 @@ use crate::{ }; use bevy::{ app::Plugin, - asset::{Asset, Assets, RenderAssetUsages}, + asset::{Assets, RenderAssetUsages}, color::{Color, ColorToComponents, Srgba}, math::{bounding::Aabb3d, Isometry3d}, - pbr::{MaterialExtension, MeshMaterial3d, StandardMaterial}, + pbr::{MeshMaterial3d, StandardMaterial}, prelude::{ AlphaMode, Commands, GlobalTransform, Mesh, Mesh3d, ResMut, Single, Transform, With, }, - reflect::Reflect, - render::{ - primitives::Aabb, - render_resource::{AsBindGroup, ShaderRef}, - }, }; use color_eyre::eyre::Result; use glam::{Vec3, Vec3A}; @@ -33,24 +28,25 @@ pub struct Lines { } impl Lines { pub fn add_to(node: &Arc, lines: Vec) -> Result> { - let _ = node + *node .get_aspect::() .unwrap() .bounding_box_calc - .set(|node| { - if let Ok(lines) = node.get_aspect::() { - return Aabb3d::from_point_cloud( - Isometry3d::IDENTITY, - lines - .data - .lock() - .iter() - .flat_map(|line| line.points.iter()) - .map(|point| Vec3A::from(point.point)), - ); - } + .lock() = { + if let Ok(lines) = node.get_aspect::() { + Aabb3d::from_point_cloud( + Isometry3d::IDENTITY, + lines + .data + .lock() + .iter() + .flat_map(|line| line.points.iter()) + .map(|point| Vec3A::from(point.point)), + ) + } else { Aabb3d::new(Vec3A::ZERO, Vec3A::ZERO) - }); + } + }; let lines = LINES_REGISTRY.add(Lines { space: node.get_aspect::()?.clone(), diff --git a/src/nodes/drawable/text.rs b/src/nodes/drawable/text.rs index ed1ac3c..f5e2dcb 100644 --- a/src/nodes/drawable/text.rs +++ b/src/nodes/drawable/text.rs @@ -1,47 +1,193 @@ use crate::{ + bevy_plugin::convert_linear_rgba, core::{ client::Client, destroy_queue, error::Result, registry::Registry, resource::get_resource_file, }, - nodes::{spatial::Spatial, Node}, + nodes::{spatial::Spatial, Aspect, Node}, + DefaultMaterial, +}; +use bevy::{ + app::{App, Plugin}, + asset::{AssetServer, Assets, RenderAssetUsages}, + color::Color, + image::Image, + pbr::MeshMaterial3d, + prelude::{ + default, BuildChildren as _, Camera, Camera2d, ChildBuild as _, Commands, Deref, Entity, + Mesh, Mesh3d, Plane3d, Query, Res, ResMut, Resource, Transform, + }, + render::{ + camera::RenderTarget, + render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages}, + }, + text::{cosmic_text::Align, JustifyText, TextColor, TextFont}, + ui::{AlignItems, BackgroundColor, FlexDirection, JustifyContent, TargetCamera, Val}, }; use color_eyre::eyre::eyre; -use glam::{vec3, Mat4, Vec2}; +use glam::{vec3, Mat4, Vec2, Vec3}; use once_cell::sync::OnceCell; use parking_lot::Mutex; use std::{ffi::OsStr, path::PathBuf, sync::Arc}; -use stereokit_rust::{ - font::Font, - sk::MainThreadToken, - system::{TextAlign, TextFit, TextStyle as SkTextStyle}, - util::{Color128, Color32}, -}; use super::{TextAspect, TextStyle}; static TEXT_REGISTRY: Registry = Registry::new(); -fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign { - match (x_align, y_align) { - (super::XAlign::Left, super::YAlign::Top) => TextAlign::TopLeft, - (super::XAlign::Left, super::YAlign::Center) => TextAlign::CenterLeft, - (super::XAlign::Left, super::YAlign::Bottom) => TextAlign::BottomLeft, - (super::XAlign::Center, super::YAlign::Top) => TextAlign::Center, - (super::XAlign::Center, super::YAlign::Center) => TextAlign::Center, - (super::XAlign::Center, super::YAlign::Bottom) => TextAlign::BottomCenter, - (super::XAlign::Right, super::YAlign::Top) => TextAlign::TopRight, - (super::XAlign::Right, super::YAlign::Center) => TextAlign::CenterRight, - (super::XAlign::Right, super::YAlign::Bottom) => TextAlign::BottomRight, +// fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> bevy::text::Text2d { +// match (x_align, y_align) { +// (super::XAlign::Left, super::YAlign::Top) => TextAlign::TopLeft, +// (super::XAlign::Left, super::YAlign::Center) => TextAlign::CenterLeft, +// (super::XAlign::Left, super::YAlign::Bottom) => TextAlign::BottomLeft, +// (super::XAlign::Center, super::YAlign::Top) => TextAlign::Center, +// (super::XAlign::Center, super::YAlign::Center) => TextAlign::Center, +// (super::XAlign::Center, super::YAlign::Bottom) => TextAlign::BottomCenter, +// (super::XAlign::Right, super::YAlign::Top) => TextAlign::TopRight, +// (super::XAlign::Right, super::YAlign::Center) => TextAlign::CenterRight, +// (super::XAlign::Right, super::YAlign::Bottom) => TextAlign::BottomRight, +// } +// } + +pub struct StardustTextPlugin; +impl Plugin for StardustTextPlugin { + fn build(&self, app: &mut App) { + let (tx, rx) = crossbeam_channel::unbounded(); + SPAWN_TEXT_SENDER.set(tx); + app.insert_resource(SpawnTextReader(rx)); + todo!() } } +fn update_text(mut surface_query: Query<(&mut Transform)>) { + for text in TEXT_REGISTRY.get_valid_contents() { + let Some((mut transform)) = text + .surface + .get() + .and_then(|v| surface_query.get_mut(*v).ok()) + else { + continue; + }; + let data = text.data.lock(); + + *transform = Transform::from_matrix( + text.space.global_transform() + * Mat4::from_scale(vec3( + data.character_height, + data.character_height, + data.character_height, + )), + ); + } +} + +fn spawn_text( + reader: Res, + mut cmds: Commands, + mut images: ResMut>, + mut meshes: ResMut>, + mut mats: ResMut>, + asset_server: Res, +) { + for text in reader.try_iter() { + let data = text.data.lock(); + let size = Extent3d { + width: (512.0 * data.bounds.as_ref().map(|v| v.bounds.x).unwrap_or(1.0)).floor() as u32, + height: (512.0 * data.bounds.as_ref().map(|v| v.bounds.y).unwrap_or(1.0)).floor() + as u32, + ..default() + }; + + // This is the texture that will be rendered to. + let mut image = Image::new_fill( + size, + TextureDimension::D2, + &[0, 0, 0, 0], + TextureFormat::Bgra8UnormSrgb, + RenderAssetUsages::default(), + ); + // You need to set these texture usage flags in order to use the image as a render target + image.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT; + + let image_handle = images.add(image); + + let cam = cmds + .spawn(( + Camera2d, + Camera { + target: RenderTarget::Image(image_handle.clone()), + ..default() + }, + )) + .id(); + let font = text + .font_path + .as_ref() + .map(|v| asset_server.load(v.as_path())); + + let ui_root = cmds + .spawn(( + bevy::ui::Node { + // Cover the whole image + width: Val::Percent(100.), + height: Val::Percent(100.), + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + BackgroundColor(Color::NONE), + TargetCamera(cam), + )) + .with_children(|parent| { + parent.spawn(( + bevy::prelude::Text::new(text.text.lock().as_str()), + TextFont { + font: font.unwrap_or_else(|| TextFont::default().font), + font_size: 40.0, + ..default() + }, + TextColor(convert_linear_rgba(data.color).into()), + )); + }) + .id(); + let surface = cmds + .spawn(( + Mesh3d( + meshes.add(Plane3d::new( + Vec3::NEG_Z, + data.bounds + .as_ref() + .map(|v| v.bounds.into()) + .unwrap_or(Vec2::ZERO) + * 0.5, + )), + ), + MeshMaterial3d(mats.add(DefaultMaterial { + base_color_texture: Some(image_handle), + unlit: true, + ..default() + })), + )) + .id(); + text.cam_entity.set(cam); + text.ui_root.set(ui_root); + text.surface.set(surface); + } +} +static SPAWN_TEXT_SENDER: OnceCell>> = OnceCell::new(); +#[derive(Resource, Deref)] +struct SpawnTextReader(crossbeam_channel::Receiver>); + pub struct Text { space: Arc, font_path: Option, - style: OnceCell, - text: Mutex, data: Mutex, + cam_entity: OnceCell, + ui_root: OnceCell, + surface: OnceCell, } impl Text { pub fn add_to(node: &Arc, text: String, style: TextStyle) -> Result> { @@ -51,86 +197,92 @@ impl Text { font_path: style.font.as_ref().and_then(|res| { get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")]) }), - style: OnceCell::new(), - text: Mutex::new(text), data: Mutex::new(style), + ui_root: OnceCell::new(), + cam_entity: OnceCell::new(), + surface: OnceCell::new(), }); node.add_aspect_raw(text.clone()); + if let Some(sender) = SPAWN_TEXT_SENDER.get() { + sender.send(text.clone()); + } Ok(text) } - fn draw(&self, token: &MainThreadToken) { - let style = self.style.get_or_try_init(|| -> Result { - let font = self - .font_path - .as_deref() - .and_then(|path| Font::from_file(path).ok()) - .unwrap_or_default(); - Ok(SkTextStyle::from_font(font, 1.0, Color32::WHITE)) - }); - - if let Ok(style) = style { - let text = self.text.lock(); - let data = self.data.lock(); - let transform = self.space.global_transform() - * Mat4::from_scale(vec3( - data.character_height, - data.character_height, - data.character_height, - )); - if let Some(bounds) = &data.bounds { - stereokit_rust::system::Text::add_in( - token, - &*text, - transform, - Vec2::from(bounds.bounds) / data.character_height, - match bounds.fit { - super::TextFit::Wrap => TextFit::Wrap, - super::TextFit::Clip => TextFit::Clip, - super::TextFit::Squeeze => TextFit::Squeeze, - super::TextFit::Exact => TextFit::Exact, - super::TextFit::Overflow => TextFit::Overflow, - }, - Some(*style), - Some(Color128::new( - data.color.c.r, - data.color.c.g, - data.color.c.b, - data.color.a, - )), - data.bounds - .as_ref() - .map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)), - Some(convert_align(data.text_align_x, data.text_align_y)), - None, - None, - None, - ); - } else { - stereokit_rust::system::Text::add_at( - token, - &*text, - transform, - Some(*style), - Some(Color128::new( - data.color.c.r, - data.color.c.g, - data.color.c.b, - data.color.a, - )), - data.bounds - .as_ref() - .map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)), - Some(convert_align(data.text_align_x, data.text_align_y)), - None, - None, - None, - ); - } - } - } + // fn draw(&self, token: &MainThreadToken) { + // let style = + // self.style + // .get_or_try_init(|| -> Result { + // let font = self + // .font_path + // .as_deref() + // .and_then(|path| Font::from_file(path).ok()) + // .unwrap_or_default(); + // Ok(SkTextStyle::from_font(font, 1.0, Color32::WHITE)) + // }); + // + // if let Ok(style) = style { + // let text = self.text.lock(); + // let data = self.data.lock(); + // let transform = self.space.global_transform() + // * Mat4::from_scale(vec3( + // data.character_height, + // data.character_height, + // data.character_height, + // )); + // if let Some(bounds) = &data.bounds { + // stereokit_rust::system::Text::add_in( + // token, + // &*text, + // transform, + // Vec2::from(bounds.bounds) / data.character_height, + // match bounds.fit { + // super::TextFit::Wrap => TextFit::Wrap, + // super::TextFit::Clip => TextFit::Clip, + // super::TextFit::Squeeze => TextFit::Squeeze, + // super::TextFit::Exact => TextFit::Exact, + // super::TextFit::Overflow => TextFit::Overflow, + // }, + // Some(*style), + // Some(Color128::new( + // data.color.c.r, + // data.color.c.g, + // data.color.c.b, + // data.color.a, + // )), + // data.bounds + // .as_ref() + // .map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)), + // Some(convert_align(data.text_align_x, data.text_align_y)), + // None, + // None, + // None, + // ); + // } else { + // stereokit_rust::system::Text::add_at( + // token, + // &*text, + // transform, + // Some(*style), + // Some(Color128::new( + // data.color.c.r, + // data.color.c.g, + // data.color.c.b, + // data.color.a, + // )), + // data.bounds + // .as_ref() + // .map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)), + // Some(convert_align(data.text_align_x, data.text_align_y)), + // None, + // None, + // None, + // ); + // } + // } + // } } impl TextAspect for Text { fn set_character_height( @@ -151,19 +303,6 @@ impl TextAspect for Text { } impl Drop for Text { fn drop(&mut self) { - if let Some(style) = self.style.take() { - destroy_queue::add(style); - } TEXT_REGISTRY.remove(self); } } - -pub fn draw_all(token: &MainThreadToken) { - for text in TEXT_REGISTRY.get_valid_contents() { - if let Some(node) = text.space.node() { - if node.enabled() { - text.draw(token); - } - } - } -}