refactor: add text support
Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
15
Cargo.toml
15
Cargo.toml
@@ -51,10 +51,16 @@ auto_link_exclude_list = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = { version = "0.15", features = ["wayland", "mp3", "wav", "trace_tracy"] }
|
bevy = { version = "0.15.1", features = [
|
||||||
bevy_mod_xr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "0.15rc" }
|
"wayland",
|
||||||
bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "0.15rc" }
|
"mp3",
|
||||||
bevy_xr_utils = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "0.15rc" }
|
"wav",
|
||||||
|
"trace_tracy",
|
||||||
|
] }
|
||||||
|
bevy_mod_xr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "wgpu_feature_fix" }
|
||||||
|
bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "wgpu_feature_fix" }
|
||||||
|
bevy_xr_utils = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "wgpu_feature_fix" }
|
||||||
|
bevy_mod_meshtext.git = "https://github.com/Schmarni-Dev/bevy_mod_meshtext"
|
||||||
openxr = "0.19"
|
openxr = "0.19"
|
||||||
winit = { version = "0.30", default-features = false, features = [] }
|
winit = { version = "0.30", default-features = false, features = [] }
|
||||||
# small utility thingys
|
# small utility thingys
|
||||||
@@ -97,6 +103,7 @@ directories = "5.0.1"
|
|||||||
xkbcommon-rs = "0.1.0"
|
xkbcommon-rs = "0.1.0"
|
||||||
thiserror = "2.0.9"
|
thiserror = "2.0.9"
|
||||||
crossbeam-channel = "0.5.14"
|
crossbeam-channel = "0.5.14"
|
||||||
|
atomicow = "1.0.0"
|
||||||
|
|
||||||
# wayland
|
# wayland
|
||||||
# wayland-scanner = "0.31.2"
|
# wayland-scanner = "0.31.2"
|
||||||
|
|||||||
BIN
src/FiraMono-subset.ttf
Normal file
BIN
src/FiraMono-subset.ttf
Normal file
Binary file not shown.
20
src/main.rs
20
src/main.rs
@@ -17,13 +17,14 @@ use bevy::app::{
|
|||||||
App, AppExit, PluginGroup, PluginsState, PostUpdate, ScheduleRunnerPlugin, Startup,
|
App, AppExit, PluginGroup, PluginsState, PostUpdate, ScheduleRunnerPlugin, Startup,
|
||||||
TerminalCtrlCHandlerPlugin, Update,
|
TerminalCtrlCHandlerPlugin, Update,
|
||||||
};
|
};
|
||||||
use bevy::asset::{AssetPlugin, AssetServer, Handle};
|
use bevy::asset::{load_internal_binary_asset, AssetApp as _, AssetPlugin, AssetServer, Handle};
|
||||||
use bevy::audio::AudioPlugin;
|
use bevy::audio::AudioPlugin;
|
||||||
use bevy::color::Color;
|
use bevy::color::Color;
|
||||||
use bevy::core_pipeline::{CorePipelinePlugin, Skybox};
|
use bevy::core_pipeline::{CorePipelinePlugin, Skybox};
|
||||||
use bevy::gizmos::GizmoPlugin;
|
use bevy::gizmos::GizmoPlugin;
|
||||||
use bevy::gltf::GltfPlugin;
|
use bevy::gltf::GltfPlugin;
|
||||||
use bevy::image::Image;
|
use bevy::image::Image;
|
||||||
|
use bevy::input::InputPlugin;
|
||||||
use bevy::log::LogPlugin;
|
use bevy::log::LogPlugin;
|
||||||
use bevy::pbr::{PbrPlugin, StandardMaterial};
|
use bevy::pbr::{PbrPlugin, StandardMaterial};
|
||||||
use bevy::prelude::{
|
use bevy::prelude::{
|
||||||
@@ -34,11 +35,13 @@ use bevy::prelude::{
|
|||||||
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
|
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
|
||||||
use bevy::render::RenderPlugin;
|
use bevy::render::RenderPlugin;
|
||||||
use bevy::scene::ScenePlugin;
|
use bevy::scene::ScenePlugin;
|
||||||
|
use bevy::text::{Font, FontLoader, TextPlugin};
|
||||||
use bevy::time::Time;
|
use bevy::time::Time;
|
||||||
use bevy::utils::default;
|
use bevy::utils::default;
|
||||||
use bevy::window::WindowPlugin;
|
use bevy::window::WindowPlugin;
|
||||||
use bevy::winit::{EventLoopProxyWrapper, WakeUp, WinitPlugin};
|
use bevy::winit::{EventLoopProxyWrapper, WakeUp, WinitPlugin};
|
||||||
use bevy::{DefaultPlugins, MinimalPlugins};
|
use bevy::{DefaultPlugins, MinimalPlugins};
|
||||||
|
use bevy_mod_meshtext::MeshTextPlugin;
|
||||||
use bevy_mod_openxr::action_set_syncing::{OxrActionSyncingPlugin, OxrSyncActionSet};
|
use bevy_mod_openxr::action_set_syncing::{OxrActionSyncingPlugin, OxrSyncActionSet};
|
||||||
use bevy_mod_openxr::exts::OxrExtensions;
|
use bevy_mod_openxr::exts::OxrExtensions;
|
||||||
use bevy_mod_openxr::features::overlay::{OxrOverlaySessionEvent, OxrOverlaySettings};
|
use bevy_mod_openxr::features::overlay::{OxrOverlaySessionEvent, OxrOverlaySettings};
|
||||||
@@ -267,6 +270,7 @@ fn bevy_loop(
|
|||||||
.disable::<ScheduleRunnerPlugin>()
|
.disable::<ScheduleRunnerPlugin>()
|
||||||
.add(TransformPlugin)
|
.add(TransformPlugin)
|
||||||
.add(HierarchyPlugin)
|
.add(HierarchyPlugin)
|
||||||
|
.add(InputPlugin)
|
||||||
.add(AccessibilityPlugin);
|
.add(AccessibilityPlugin);
|
||||||
base = match headless {
|
base = match headless {
|
||||||
true => {
|
true => {
|
||||||
@@ -343,15 +347,25 @@ fn bevy_loop(
|
|||||||
bevy_app.add_event::<OxrSyncActionSet>();
|
bevy_app.add_event::<OxrSyncActionSet>();
|
||||||
bevy_app.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin);
|
bevy_app.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin);
|
||||||
}
|
}
|
||||||
|
bevy_app
|
||||||
|
.init_asset::<Font>()
|
||||||
|
.init_asset_loader::<FontLoader>();
|
||||||
|
bevy_app.add_plugins(MeshTextPlugin);
|
||||||
bevy_app.add_plugins(StardustBevyPlugin);
|
bevy_app.add_plugins(StardustBevyPlugin);
|
||||||
bevy_app.add_plugins((
|
bevy_app.add_plugins((
|
||||||
BevyLinesPlugin,
|
BevyLinesPlugin,
|
||||||
StardustModelPlugin,
|
StardustModelPlugin,
|
||||||
StardustHandPlugin,
|
StardustHandPlugin,
|
||||||
// StardustTextPlugin,
|
StardustTextPlugin,
|
||||||
StardustSoundPlugin,
|
StardustSoundPlugin,
|
||||||
StardustControllerPlugin,
|
StardustControllerPlugin,
|
||||||
));
|
));
|
||||||
|
load_internal_binary_asset!(
|
||||||
|
bevy_app,
|
||||||
|
Handle::default(),
|
||||||
|
"FiraMono-subset.ttf",
|
||||||
|
|bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() }
|
||||||
|
);
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
struct SkyTexture(Handle<Image>);
|
struct SkyTexture(Handle<Image>);
|
||||||
// Skytex/light stuff
|
// Skytex/light stuff
|
||||||
@@ -482,7 +496,7 @@ fn bevy_loop(
|
|||||||
.flatten()
|
.flatten()
|
||||||
.map(|mut waiter| {
|
.map(|mut waiter| {
|
||||||
TOKIO.spawn_blocking(move || {
|
TOKIO.spawn_blocking(move || {
|
||||||
let _span = debug_span!("eeping").entered();
|
let _span = debug_span!("frame eeping").entered();
|
||||||
let result = waiter.wait();
|
let result = waiter.wait();
|
||||||
(waiter, result)
|
(waiter, result)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -162,8 +162,9 @@ pub fn draw_all(
|
|||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
mut cmds: Commands,
|
mut cmds: Commands,
|
||||||
hmd: Single<&GlobalTransform, With<ViewLocation>>,
|
hmd: Option<Single<&GlobalTransform, With<ViewLocation>>>,
|
||||||
) {
|
) {
|
||||||
|
let Some(hmd) = hmd else { return };
|
||||||
let material = StandardMaterial {
|
let material = StandardMaterial {
|
||||||
base_color: Color::WHITE,
|
base_color: Color::WHITE,
|
||||||
alpha_mode: AlphaMode::Blend,
|
alpha_mode: AlphaMode::Blend,
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ use crate::nodes::spatial::Spatial;
|
|||||||
use crate::nodes::Node;
|
use crate::nodes::Node;
|
||||||
use crate::DefaultMaterial;
|
use crate::DefaultMaterial;
|
||||||
use bevy::app::{Plugin, PostUpdate, PreUpdate, Update};
|
use bevy::app::{Plugin, PostUpdate, PreUpdate, Update};
|
||||||
use bevy::asset::{AssetServer, Assets};
|
use bevy::asset::{AssetServer, Assets, Handle};
|
||||||
use bevy::color::{Color, LinearRgba};
|
use bevy::color::{Color, LinearRgba};
|
||||||
use bevy::core::Name;
|
use bevy::core::Name;
|
||||||
use bevy::gltf::GltfAssetLabel;
|
use bevy::gltf::GltfAssetLabel;
|
||||||
|
use bevy::image::Image;
|
||||||
use bevy::math::bounding::Aabb3d;
|
use bevy::math::bounding::Aabb3d;
|
||||||
use bevy::pbr::MeshMaterial3d;
|
use bevy::pbr::MeshMaterial3d;
|
||||||
use bevy::prelude::{
|
use bevy::prelude::{
|
||||||
BuildChildrenTransformExt, Children, Commands, Component, Deref, Entity, Has,
|
AlphaMode, BuildChildrenTransformExt, Children, Commands, Component, Deref, Entity, Has,
|
||||||
HierarchyQueryExt, Parent, Query, Res, ResMut, Resource, Transform, Visibility, With, Without,
|
HierarchyQueryExt, Parent, Query, Res, ResMut, Resource, Transform, Visibility, With, Without,
|
||||||
};
|
};
|
||||||
use bevy::reflect::GetField;
|
use bevy::reflect::GetField;
|
||||||
@@ -42,6 +43,7 @@ impl MaterialParameter {
|
|||||||
client: &Client,
|
client: &Client,
|
||||||
material: &mut DefaultMaterial,
|
material: &mut DefaultMaterial,
|
||||||
parameter_name: &str,
|
parameter_name: &str,
|
||||||
|
asset_server: &AssetServer,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
MaterialParameter::Bool(val) => match parameter_name {
|
MaterialParameter::Bool(val) => match parameter_name {
|
||||||
@@ -58,7 +60,7 @@ impl MaterialParameter {
|
|||||||
if let Some(field) = material.get_field_mut::<i32>(name) {
|
if let Some(field) = material.get_field_mut::<i32>(name) {
|
||||||
*field = *val;
|
*field = *val;
|
||||||
} else {
|
} else {
|
||||||
warn!("unknown bool material parameter name: {name}");
|
warn!("unknown i32 material parameter name: {name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -67,16 +69,23 @@ impl MaterialParameter {
|
|||||||
if let Some(field) = material.get_field_mut::<u32>(name) {
|
if let Some(field) = material.get_field_mut::<u32>(name) {
|
||||||
*field = *val;
|
*field = *val;
|
||||||
} else {
|
} else {
|
||||||
warn!("unknown bool material parameter name: {name}");
|
warn!("unknown u32 material parameter name: {name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MaterialParameter::Float(val) => match parameter_name {
|
MaterialParameter::Float(val) => match parameter_name {
|
||||||
|
"cutoff" => {
|
||||||
|
// should this only set the value if AlphaMode is already AlphaMode::Mask?
|
||||||
|
material.alpha_mode = AlphaMode::Mask(*val);
|
||||||
|
}
|
||||||
|
"metallic" => {
|
||||||
|
material.metallic = *val;
|
||||||
|
}
|
||||||
name => {
|
name => {
|
||||||
if let Some(field) = material.get_field_mut::<f32>(name) {
|
if let Some(field) = material.get_field_mut::<f32>(name) {
|
||||||
*field = *val;
|
*field = *val;
|
||||||
} else {
|
} else {
|
||||||
warn!("unknown bool material parameter name: {name}");
|
warn!("unknown f32 material parameter name: {name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -85,7 +94,7 @@ impl MaterialParameter {
|
|||||||
if let Some(field) = material.get_field_mut::<Vec2>(name) {
|
if let Some(field) = material.get_field_mut::<Vec2>(name) {
|
||||||
*field = (*val).into();
|
*field = (*val).into();
|
||||||
} else {
|
} else {
|
||||||
warn!("unknown bool material parameter name: {name}");
|
warn!("unknown vec2 material parameter name: {name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -94,7 +103,7 @@ impl MaterialParameter {
|
|||||||
if let Some(field) = material.get_field_mut::<Vec3>(name) {
|
if let Some(field) = material.get_field_mut::<Vec3>(name) {
|
||||||
*field = (*val).into();
|
*field = (*val).into();
|
||||||
} else {
|
} else {
|
||||||
warn!("unknown bool material parameter name: {name}");
|
warn!("unknown vec3 material parameter name: {name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -102,25 +111,46 @@ impl MaterialParameter {
|
|||||||
"color" => {
|
"color" => {
|
||||||
material.base_color = LinearRgba::new(val.c.r, val.c.g, val.c.b, val.a).into()
|
material.base_color = LinearRgba::new(val.c.r, val.c.g, val.c.b, val.a).into()
|
||||||
}
|
}
|
||||||
|
"emission_factor" => {
|
||||||
|
material.emissive = LinearRgba::new(val.c.r, val.c.g, val.c.b, val.a)
|
||||||
|
}
|
||||||
name => {
|
name => {
|
||||||
if let Some(field) = material.get_field_mut::<Color>(name) {
|
if let Some(field) = material.get_field_mut::<Color>(name) {
|
||||||
*field = LinearRgba::new(val.c.r, val.c.g, val.c.b, val.a).into();
|
*field = LinearRgba::new(val.c.r, val.c.g, val.c.b, val.a).into();
|
||||||
} else {
|
} else {
|
||||||
warn!("unknown bool material parameter name: {name}");
|
warn!("unknown color material parameter name: {name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MaterialParameter::Texture(resource) => {
|
MaterialParameter::Texture(resource) => {
|
||||||
match parameter_name {
|
|
||||||
name => {
|
|
||||||
warn!("unknown texture material parameter name: {name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let Some(texture_path) =
|
let Some(texture_path) =
|
||||||
get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")])
|
get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")])
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let image = asset_server.load::<Image>(texture_path);
|
||||||
|
match parameter_name {
|
||||||
|
"diffuse" => {
|
||||||
|
material.base_color_texture.replace(image);
|
||||||
|
}
|
||||||
|
"emission" => {
|
||||||
|
material.emissive_texture.replace(image);
|
||||||
|
}
|
||||||
|
"normal" => {
|
||||||
|
material.normal_map_texture.replace(image);
|
||||||
|
}
|
||||||
|
"occlusion" => {
|
||||||
|
material.occlusion_texture.replace(image);
|
||||||
|
}
|
||||||
|
// TODO: impl metalic and roughness textures, they are combined in bevy
|
||||||
|
name => {
|
||||||
|
if let Some(field) = material.get_field_mut::<Option<Handle<Image>>>(name) {
|
||||||
|
field.replace(image);
|
||||||
|
} else {
|
||||||
|
warn!("unknown texture material parameter name: {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
error!("TODO: implement texture changing");
|
error!("TODO: implement texture changing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,16 +223,18 @@ fn update_model_parts(
|
|||||||
mut part_query: Query<(
|
mut part_query: Query<(
|
||||||
&mut Transform,
|
&mut Transform,
|
||||||
&mut MeshMaterial3d<DefaultMaterial>,
|
&mut MeshMaterial3d<DefaultMaterial>,
|
||||||
|
&mut Visibility,
|
||||||
Has<Parent>,
|
Has<Parent>,
|
||||||
)>,
|
)>,
|
||||||
mut cmds: Commands,
|
mut cmds: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
) {
|
) {
|
||||||
for model in &models {
|
for model in &models {
|
||||||
let Some(model) = model.0.upgrade() else {
|
let Some(model) = model.0.upgrade() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for part in model.parts.lock().iter() {
|
for part in model.parts.lock().iter() {
|
||||||
let Some((entity, (mut transform, mut mat, has_parent))) = part
|
let Some((entity, (mut transform, mut mat, mut vis, has_parent))) = part
|
||||||
.entity
|
.entity
|
||||||
.get()
|
.get()
|
||||||
.and_then(|e| Some((*e, part_query.get_mut(*e).ok()?)))
|
.and_then(|e| Some((*e, part_query.get_mut(*e).ok()?)))
|
||||||
@@ -213,6 +245,12 @@ fn update_model_parts(
|
|||||||
cmds.entity(entity).remove_parent_in_place();
|
cmds.entity(entity).remove_parent_in_place();
|
||||||
}
|
}
|
||||||
*transform = Transform::from_matrix(part.space.global_transform());
|
*transform = Transform::from_matrix(part.space.global_transform());
|
||||||
|
if let Some(node) = part.space.node() {
|
||||||
|
*vis = match node.enabled() {
|
||||||
|
true => Visibility::Inherited,
|
||||||
|
false => Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// todo: find all materials with identical parameters and batch them into 1 material again
|
// todo: find all materials with identical parameters and batch them into 1 material again
|
||||||
'mat_params: {
|
'mat_params: {
|
||||||
@@ -231,6 +269,7 @@ fn update_model_parts(
|
|||||||
&client,
|
&client,
|
||||||
&mut new_material,
|
&mut new_material,
|
||||||
¶meter_name,
|
¶meter_name,
|
||||||
|
&asset_server,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
mat.0 = mats.add(new_material);
|
mat.0 = mats.add(new_material);
|
||||||
|
|||||||
@@ -1,61 +1,50 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bevy_plugin::convert_linear_rgba,
|
bevy_plugin::{convert_linear_rgba, DESTROY_ENTITY},
|
||||||
core::{
|
core::{
|
||||||
client::Client,
|
client::Client,
|
||||||
destroy_queue,
|
|
||||||
error::{Result, ServerError},
|
error::{Result, ServerError},
|
||||||
registry::Registry,
|
registry::Registry,
|
||||||
resource::get_resource_file,
|
resource::get_resource_file,
|
||||||
},
|
},
|
||||||
nodes::{spatial::Spatial, Aspect, Node},
|
nodes::{spatial::Spatial, Node},
|
||||||
DefaultMaterial,
|
DefaultMaterial,
|
||||||
};
|
};
|
||||||
use bevy::{
|
use bevy::{
|
||||||
app::{App, Plugin, PostUpdate, PreUpdate},
|
app::{App, Plugin, PostUpdate, PreUpdate},
|
||||||
asset::{AssetServer, Assets, RenderAssetUsages},
|
asset::{AssetServer, Assets},
|
||||||
color::Color,
|
|
||||||
image::Image,
|
|
||||||
pbr::MeshMaterial3d,
|
pbr::MeshMaterial3d,
|
||||||
prelude::{
|
prelude::{Commands, Deref, Entity, Query, Res, ResMut, Resource, Transform},
|
||||||
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 glam::{vec3, Mat4, Vec2, Vec3};
|
use bevy_mod_meshtext::{HorizontalLayout, MeshText, MeshTextFont, VerticalLayout};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||||
use tracing::{info, info_span};
|
use tracing::info_span;
|
||||||
|
|
||||||
use super::{TextAspect, TextStyle};
|
use super::{TextAspect, TextStyle};
|
||||||
|
|
||||||
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
||||||
|
|
||||||
// fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> bevy::text::Text2d {
|
const fn convert_align_x(x_align: super::XAlign) -> HorizontalLayout {
|
||||||
// match (x_align, y_align) {
|
match x_align {
|
||||||
// (super::XAlign::Left, super::YAlign::Top) => TextAlign::TopLeft,
|
super::XAlign::Left => HorizontalLayout::Left,
|
||||||
// (super::XAlign::Left, super::YAlign::Center) => TextAlign::CenterLeft,
|
super::XAlign::Center => HorizontalLayout::Centered,
|
||||||
// (super::XAlign::Left, super::YAlign::Bottom) => TextAlign::BottomLeft,
|
super::XAlign::Right => HorizontalLayout::Right,
|
||||||
// (super::XAlign::Center, super::YAlign::Top) => TextAlign::Center,
|
}
|
||||||
// (super::XAlign::Center, super::YAlign::Center) => TextAlign::Center,
|
}
|
||||||
// (super::XAlign::Center, super::YAlign::Bottom) => TextAlign::BottomCenter,
|
const fn convert_align_y(y_align: super::YAlign) -> VerticalLayout {
|
||||||
// (super::XAlign::Right, super::YAlign::Top) => TextAlign::TopRight,
|
match y_align {
|
||||||
// (super::XAlign::Right, super::YAlign::Center) => TextAlign::CenterRight,
|
super::YAlign::Top => VerticalLayout::Top,
|
||||||
// (super::XAlign::Right, super::YAlign::Bottom) => TextAlign::BottomRight,
|
super::YAlign::Center => VerticalLayout::Centered,
|
||||||
// }
|
super::YAlign::Bottom => VerticalLayout::Bottom,
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct StardustTextPlugin;
|
pub struct StardustTextPlugin;
|
||||||
impl Plugin for StardustTextPlugin {
|
impl Plugin for StardustTextPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
let (tx, rx) = crossbeam_channel::unbounded();
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
SPAWN_TEXT_SENDER.set(tx);
|
_ = SPAWN_TEXT_SENDER.set(tx);
|
||||||
app.insert_resource(SpawnTextReader(rx));
|
app.insert_resource(SpawnTextReader(rx));
|
||||||
app.add_systems(PostUpdate, update_text);
|
app.add_systems(PostUpdate, update_text);
|
||||||
app.add_systems(PreUpdate, spawn_text);
|
app.add_systems(PreUpdate, spawn_text);
|
||||||
@@ -65,7 +54,7 @@ impl Plugin for StardustTextPlugin {
|
|||||||
fn update_text(mut surface_query: Query<(&mut Transform)>) {
|
fn update_text(mut surface_query: Query<(&mut Transform)>) {
|
||||||
for text in TEXT_REGISTRY.get_valid_contents() {
|
for text in TEXT_REGISTRY.get_valid_contents() {
|
||||||
let Some((mut transform)) = text
|
let Some((mut transform)) = text
|
||||||
.surface
|
.entity
|
||||||
.get()
|
.get()
|
||||||
.and_then(|v| surface_query.get_mut(*v).ok())
|
.and_then(|v| surface_query.get_mut(*v).ok())
|
||||||
else {
|
else {
|
||||||
@@ -73,21 +62,13 @@ fn update_text(mut surface_query: Query<(&mut Transform)>) {
|
|||||||
};
|
};
|
||||||
// let data = text.data.lock();
|
// let data = text.data.lock();
|
||||||
|
|
||||||
*transform = Transform::from_matrix(
|
*transform = Transform::from_matrix(text.space.global_transform());
|
||||||
text.space.global_transform(), // * Mat4::from_scale(vec3(
|
|
||||||
// data.character_height,
|
|
||||||
// data.character_height,
|
|
||||||
// data.character_height,
|
|
||||||
// )),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_text(
|
fn spawn_text(
|
||||||
reader: Res<SpawnTextReader>,
|
reader: Res<SpawnTextReader>,
|
||||||
mut cmds: Commands,
|
mut cmds: Commands,
|
||||||
mut images: ResMut<Assets<Image>>,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut mats: ResMut<Assets<DefaultMaterial>>,
|
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
) {
|
) {
|
||||||
@@ -96,99 +77,36 @@ fn spawn_text(
|
|||||||
let _span2 = info_span!("text data lock").entered();
|
let _span2 = info_span!("text data lock").entered();
|
||||||
let data = text.data.lock();
|
let data = text.data.lock();
|
||||||
drop(_span2);
|
drop(_span2);
|
||||||
let size = Extent3d {
|
let _span2 = info_span!("text str lock").entered();
|
||||||
width: (512.0 * data.bounds.as_ref().map(|v| v.bounds.x).unwrap_or(1.0)).floor() as u32,
|
let str = text.text.lock().clone();
|
||||||
height: (512.0 * data.bounds.as_ref().map(|v| v.bounds.y).unwrap_or(1.0)).floor()
|
drop(_span2);
|
||||||
as u32,
|
let mat = mats.add(DefaultMaterial {
|
||||||
..default()
|
base_color: convert_linear_rgba(data.color).into(),
|
||||||
};
|
unlit: true,
|
||||||
|
..Default::default()
|
||||||
let _span = info_span!("create and setup image").entered();
|
});
|
||||||
// This is the texture that will be rendered to.
|
|
||||||
let mut image = Image::transparent();
|
|
||||||
image.texture_descriptor.format = TextureFormat::Bgra8UnormSrgb;
|
|
||||||
image.texture_descriptor.dimension = TextureDimension::D2;
|
|
||||||
image.asset_usage = RenderAssetUsages::default();
|
|
||||||
image.resize(size);
|
|
||||||
// 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);
|
|
||||||
drop(_span);
|
|
||||||
|
|
||||||
let _span = info_span!("spawn text camera and load font").entered();
|
|
||||||
let cam = cmds
|
|
||||||
.spawn((
|
|
||||||
Camera2d,
|
|
||||||
Camera {
|
|
||||||
target: RenderTarget::Image(image_handle.clone()),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.id();
|
|
||||||
let font = text
|
let font = text
|
||||||
.font_path
|
.font_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|v| asset_server.load(v.as_path()));
|
.map(|p| asset_server.load(p.as_path()));
|
||||||
drop(_span);
|
let mut text_entity = cmds.spawn((
|
||||||
|
MeshText {
|
||||||
|
text: atomicow::CowArc::Owned(str),
|
||||||
|
height: data.character_height,
|
||||||
|
depth: 0.0,
|
||||||
|
},
|
||||||
|
MeshMaterial3d(mat),
|
||||||
|
convert_align_x(data.text_align_x),
|
||||||
|
convert_align_y(data.text_align_y),
|
||||||
|
));
|
||||||
|
if let Some(font) = font {
|
||||||
|
text_entity.insert(MeshTextFont(font));
|
||||||
|
}
|
||||||
|
|
||||||
|
let entity = text_entity.id();
|
||||||
|
|
||||||
let _span = info_span!("spawn ui entities").entered();
|
|
||||||
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();
|
|
||||||
drop(_span);
|
|
||||||
let _span = info_span!("spawn 3d plane").entered();
|
|
||||||
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,
|
|
||||||
// would Cutout be enough here?
|
|
||||||
alpha_mode: bevy::prelude::AlphaMode::Blend,
|
|
||||||
..default()
|
|
||||||
})),
|
|
||||||
))
|
|
||||||
.id();
|
|
||||||
drop(_span);
|
|
||||||
let _span = info_span!("setting OneCells").entered();
|
let _span = info_span!("setting OneCells").entered();
|
||||||
text.cam_entity.set(cam);
|
text.entity.set(entity);
|
||||||
text.ui_root.set(ui_root);
|
|
||||||
text.surface.set(surface);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static SPAWN_TEXT_SENDER: OnceCell<crossbeam_channel::Sender<Arc<Text>>> = OnceCell::new();
|
static SPAWN_TEXT_SENDER: OnceCell<crossbeam_channel::Sender<Arc<Text>>> = OnceCell::new();
|
||||||
@@ -198,11 +116,9 @@ struct SpawnTextReader(crossbeam_channel::Receiver<Arc<Text>>);
|
|||||||
pub struct Text {
|
pub struct Text {
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
font_path: Option<PathBuf>,
|
font_path: Option<PathBuf>,
|
||||||
text: Mutex<String>,
|
text: Mutex<Arc<str>>,
|
||||||
data: Mutex<TextStyle>,
|
data: Mutex<TextStyle>,
|
||||||
cam_entity: OnceCell<Entity>,
|
entity: OnceCell<Entity>,
|
||||||
ui_root: OnceCell<Entity>,
|
|
||||||
surface: OnceCell<Entity>,
|
|
||||||
}
|
}
|
||||||
impl Text {
|
impl Text {
|
||||||
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
|
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
|
||||||
@@ -212,11 +128,9 @@ impl Text {
|
|||||||
font_path: style.font.as_ref().and_then(|res| {
|
font_path: style.font.as_ref().and_then(|res| {
|
||||||
get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
|
get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
|
||||||
}),
|
}),
|
||||||
text: Mutex::new(text),
|
text: Mutex::new(text.into()),
|
||||||
data: Mutex::new(style),
|
data: Mutex::new(style),
|
||||||
ui_root: OnceCell::new(),
|
entity: OnceCell::new(),
|
||||||
cam_entity: OnceCell::new(),
|
|
||||||
surface: OnceCell::new(),
|
|
||||||
});
|
});
|
||||||
node.add_aspect_raw(text.clone());
|
node.add_aspect_raw(text.clone());
|
||||||
if let Some(sender) = SPAWN_TEXT_SENDER.get() {
|
if let Some(sender) = SPAWN_TEXT_SENDER.get() {
|
||||||
@@ -225,79 +139,6 @@ impl Text {
|
|||||||
|
|
||||||
Ok(text)
|
Ok(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
impl TextAspect for Text {
|
||||||
fn set_character_height(
|
fn set_character_height(
|
||||||
@@ -312,12 +153,15 @@ impl TextAspect for Text {
|
|||||||
|
|
||||||
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
|
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
|
||||||
let this_text = node.get_aspect::<Text>()?;
|
let this_text = node.get_aspect::<Text>()?;
|
||||||
*this_text.text.lock() = text;
|
*this_text.text.lock() = text.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for Text {
|
impl Drop for Text {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
if let Some(e) = self.entity.get() {
|
||||||
|
DESTROY_ENTITY.send(*e);
|
||||||
|
}
|
||||||
TEXT_REGISTRY.remove(self);
|
TEXT_REGISTRY.remove(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ use crate::core::error::{Result, ServerError};
|
|||||||
use crate::core::queued_mutex::QueuedMutex;
|
use crate::core::queued_mutex::QueuedMutex;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::core::scenegraph::MethodResponseSender;
|
use crate::core::scenegraph::MethodResponseSender;
|
||||||
use parking_lot::Mutex;
|
|
||||||
use portable_atomic::{AtomicBool, Ordering};
|
use portable_atomic::{AtomicBool, Ordering};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
@@ -21,12 +20,12 @@ use spatial::Spatial;
|
|||||||
use stardust_xr::messenger::MessageSenderHandle;
|
use stardust_xr::messenger::MessageSenderHandle;
|
||||||
use stardust_xr::scenegraph::ScenegraphError;
|
use stardust_xr::scenegraph::ScenegraphError;
|
||||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||||
|
use tracing::debug_span;
|
||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::os::fd::OwnedFd;
|
use std::os::fd::OwnedFd;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use tracing::{debug_span, info};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
@@ -345,7 +344,7 @@ impl Aspects {
|
|||||||
}
|
}
|
||||||
fn get<A: Aspect + AspectIdentifier>(&self) -> Result<Arc<A>> {
|
fn get<A: Aspect + AspectIdentifier>(&self) -> Result<Arc<A>> {
|
||||||
self.0
|
self.0
|
||||||
.lock()
|
.read_now()
|
||||||
.get(&A::ID)
|
.get(&A::ID)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|a| a.as_any())
|
.map(|a| a.as_any())
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ fn update_hands(
|
|||||||
|
|
||||||
const PINCH_MAX: f32 = 0.11;
|
const PINCH_MAX: f32 = 0.11;
|
||||||
const PINCH_ACTIVACTION_DISTANCE: f32 = 0.01;
|
const PINCH_ACTIVACTION_DISTANCE: f32 = 0.01;
|
||||||
// TODO: handle invalid data
|
|
||||||
// based on https://github.com/StereoKit/StereoKit/blob/ca2be7d45f4f4388e8df7542e9a0313bcc45946e/StereoKitC/hands/input_hand.cpp#L375-L394
|
// based on https://github.com/StereoKit/StereoKit/blob/ca2be7d45f4f4388e8df7542e9a0313bcc45946e/StereoKitC/hands/input_hand.cpp#L375-L394
|
||||||
fn pinch_activation(joints: &[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]) -> f32 {
|
fn pinch_activation(joints: &[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]) -> f32 {
|
||||||
let combined_radius =
|
let combined_radius =
|
||||||
@@ -182,7 +181,6 @@ fn pinch_activation(joints: &[openxr::HandJointLocation; openxr::HAND_JOINT_COUN
|
|||||||
|
|
||||||
const GRIP_MAX: f32 = 0.11;
|
const GRIP_MAX: f32 = 0.11;
|
||||||
const GRIP_ACTIVACTION_DISTANCE: f32 = 0.01;
|
const GRIP_ACTIVACTION_DISTANCE: f32 = 0.01;
|
||||||
// TODO: handle invalid data
|
|
||||||
// based on https://github.com/StereoKit/StereoKit/blob/ca2be7d45f4f4388e8df7542e9a0313bcc45946e/StereoKitC/hands/input_hand.cpp#L375-L394
|
// based on https://github.com/StereoKit/StereoKit/blob/ca2be7d45f4f4388e8df7542e9a0313bcc45946e/StereoKitC/hands/input_hand.cpp#L375-L394
|
||||||
fn grip_activation(joints: &[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]) -> f32 {
|
fn grip_activation(joints: &[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]) -> f32 {
|
||||||
let combined_radius = joints[HandBone::RingTip as usize].radius
|
let combined_radius = joints[HandBone::RingTip as usize].radius
|
||||||
|
|||||||
Reference in New Issue
Block a user