refactor: absolutly minimal text impl, doesn't yet care about state change or despawn
Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
@@ -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<f32, Rgb<f32, LinearRgb>>) -> 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)]
|
||||
|
||||
@@ -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<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
|
||||
let _ = node
|
||||
*node
|
||||
.get_aspect::<Spatial>()
|
||||
.unwrap()
|
||||
.bounding_box_calc
|
||||
.set(|node| {
|
||||
if let Ok(lines) = node.get_aspect::<Lines>() {
|
||||
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::<Lines>() {
|
||||
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::<Spatial>()?.clone(),
|
||||
|
||||
@@ -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<Text> = 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<SpawnTextReader>,
|
||||
mut cmds: Commands,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
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<crossbeam_channel::Sender<Arc<Text>>> = OnceCell::new();
|
||||
#[derive(Resource, Deref)]
|
||||
struct SpawnTextReader(crossbeam_channel::Receiver<Arc<Text>>);
|
||||
|
||||
pub struct Text {
|
||||
space: Arc<Spatial>,
|
||||
font_path: Option<PathBuf>,
|
||||
style: OnceCell<SkTextStyle>,
|
||||
|
||||
text: Mutex<String>,
|
||||
data: Mutex<TextStyle>,
|
||||
cam_entity: OnceCell<Entity>,
|
||||
ui_root: OnceCell<Entity>,
|
||||
surface: OnceCell<Entity>,
|
||||
}
|
||||
impl Text {
|
||||
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
|
||||
@@ -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<SkTextStyle> {
|
||||
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<SkTextStyle, color_eyre::eyre::Error> {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user