feat: mostly reimpl text rendering

Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
Schmarni
2025-06-25 11:50:57 +02:00
parent 51e8cfd1b2
commit 20d2917847
10 changed files with 411 additions and 240 deletions

154
Cargo.lock generated
View File

@@ -618,6 +618,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b8369c16b7c017437021341521f8b4a0d98e1c70113fb358c3258ae7d661d79"
dependencies = [
"bevy_internal",
"tracing",
]
[[package]]
name = "bevy-mesh-text-3d"
version = "0.1.0"
source = "git+https://github.com/terhechte/bevy-mesh-text-3d#77d8ae6935af636bcb85251b1debbc7f3d618f74"
dependencies = [
"bevy",
"cosmic-text 0.14.2",
"lyon",
"thiserror 2.0.12",
]
[[package]]
@@ -686,6 +698,7 @@ dependencies = [
"downcast-rs 2.0.1",
"log",
"thiserror 2.0.12",
"tracing",
"variadics_please",
"wasm-bindgen",
"web-sys",
@@ -861,6 +874,7 @@ dependencies = [
"serde",
"smallvec",
"thiserror 2.0.12",
"tracing",
"variadics_please",
]
@@ -1092,9 +1106,11 @@ dependencies = [
"bevy_ecs",
"bevy_utils",
"tracing",
"tracing-error",
"tracing-log",
"tracing-oslog",
"tracing-subscriber",
"tracing-tracy",
"tracing-wasm",
]
@@ -1165,16 +1181,6 @@ dependencies = [
"glam",
]
[[package]]
name = "bevy_mod_meshtext"
version = "0.1.0"
source = "git+https://github.com/Schmarni-Dev/bevy_mod_meshtext#b623971b5a4d0ee0a93a2f89096726b9b7c9c87a"
dependencies = [
"atomicow",
"bevy",
"meshtext",
]
[[package]]
name = "bevy_mod_openxr"
version = "0.3.0"
@@ -1388,11 +1394,13 @@ dependencies = [
"naga_oil",
"nonmax",
"offset-allocator",
"profiling",
"send_wrapper",
"serde",
"smallvec",
"thiserror 2.0.12",
"tracing",
"tracy-client",
"variadics_please",
"wasm-bindgen",
"web-sys",
@@ -1542,7 +1550,7 @@ dependencies = [
"bevy_transform",
"bevy_utils",
"bevy_window",
"cosmic-text",
"cosmic-text 0.13.2",
"serde",
"smallvec",
"sys-locale",
@@ -1913,16 +1921,6 @@ dependencies = [
"shlex",
]
[[package]]
name = "cdt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91b872294f63ef586b32aa94141561681aa35ca2d703960cca4f661f4e18184"
dependencies = [
"geometry-predicates",
"thiserror 1.0.69",
]
[[package]]
name = "cesu8"
version = "1.1.0"
@@ -2283,6 +2281,29 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cosmic-text"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da46a9d5a8905cc538a4a5bceb6a4510de7a51049c5588c0114efce102bcbbe8"
dependencies = [
"bitflags 2.9.1",
"fontdb",
"log",
"rangemap",
"rustc-hash 1.1.0",
"rustybuzz",
"self_cell",
"smol_str",
"swash",
"sys-locale",
"ttf-parser 0.21.1",
"unicode-bidi",
"unicode-linebreak",
"unicode-script",
"unicode-segmentation",
]
[[package]]
name = "cpal"
version = "0.15.3"
@@ -2809,6 +2830,12 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "float_next_after"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
[[package]]
name = "fnv"
version = "1.0.7"
@@ -2975,12 +3002,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "geometry-predicates"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dba84198cf199c11b83f1cb9243eaeb70dc50b719d2835ebf34bf2481bca010"
[[package]]
name = "gethostname"
version = "0.4.3"
@@ -3861,6 +3882,58 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "lyon"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f"
dependencies = [
"lyon_algorithms",
"lyon_tessellation",
]
[[package]]
name = "lyon_algorithms"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08"
dependencies = [
"lyon_path",
"num-traits",
]
[[package]]
name = "lyon_geom"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570"
dependencies = [
"arrayvec",
"euclid",
"num-traits",
]
[[package]]
name = "lyon_path"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0047f508cd7a85ad6bad9518f68cce7b1bf6b943fb71f6da0ee3bc1e8cb75f25"
dependencies = [
"lyon_geom",
"num-traits",
]
[[package]]
name = "lyon_tessellation"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c"
dependencies = [
"float_next_after",
"lyon_path",
"num-traits",
]
[[package]]
name = "mach2"
version = "0.4.2"
@@ -3930,18 +4003,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "meshtext"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c68541f06674ef4bb1f84e3dca654998135ff1a8bb5a60cfbfe5b8615f8e555"
dependencies = [
"cdt",
"glam",
"owned_ttf_parser",
"ttf-parser 0.25.1",
]
[[package]]
name = "metal"
version = "0.31.0"
@@ -4959,6 +5020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
dependencies = [
"profiling-procmacros",
"tracing",
]
[[package]]
@@ -5729,13 +5791,14 @@ name = "stardust-xr-server"
version = "0.45.0"
dependencies = [
"bevy",
"bevy_mod_meshtext",
"bevy-mesh-text-3d",
"bevy_mod_openxr",
"bevy_mod_xr",
"bevy_sk",
"clap",
"color-eyre",
"console-subscriber",
"cosmic-text 0.14.2",
"dashmap",
"directories",
"glam",
@@ -6339,8 +6402,7 @@ dependencies = [
[[package]]
name = "tracing-tracy"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba"
source = "git+https://github.com/nagisa/rust_tracy_client?tag=tracy-client-v0.18.1#7d084307f54959999ca3729c42bd4f4f3f0df54d"
dependencies = [
"tracing-core",
"tracing-subscriber",
@@ -6361,8 +6423,7 @@ dependencies = [
[[package]]
name = "tracy-client"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3927832d93178f979a970d26deed7b03510586e328f31b0f9ad7a73985b8332a"
source = "git+https://github.com/nagisa/rust_tracy_client?tag=tracy-client-v0.18.1#7d084307f54959999ca3729c42bd4f4f3f0df54d"
dependencies = [
"loom",
"once_cell",
@@ -6372,11 +6433,10 @@ dependencies = [
[[package]]
name = "tracy-client-sys"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c032d68a49d25d9012a864fef1c64ac17aee43c87e0477bf7301d8ae8bfea7b7"
source = "git+https://github.com/nagisa/rust_tracy_client?tag=tracy-client-v0.18.1#7d084307f54959999ca3729c42bd4f4f3f0df54d"
dependencies = [
"cc",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]

View File

@@ -24,7 +24,8 @@ path = "src/main.rs"
default = []
wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
profile_app = ["dep:tracing-tracy"]
profile_app = ["dep:tracing-tracy", "bevy/trace_tracy"]
change_tracking = ["bevy/track_location"]
[package.metadata.appimage]
auto_link = true
@@ -81,9 +82,10 @@ tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] }
bevy = { version = "0.16", features = ["wayland", "bevy_remote"] }
bevy_mod_xr = "0.3"
bevy_mod_openxr = "0.3"
bevy_mod_meshtext.git = "https://github.com/Schmarni-Dev/bevy_mod_meshtext"
# bevy_mod_meshtext.git = "https://github.com/Schmarni-Dev/bevy_mod_meshtext"
# bevy_sk.git = "https://github.com/MalekiRe/bevy_sk"
# bevy_sk = { git = "https://github.com/Schmarni-Dev/bevy_sk", branch = "fix_mat_stuff" }
bevy-mesh-text-3d.git = "https://github.com/terhechte/bevy-mesh-text-3d"
bevy_sk.path = "../bevy_sk"
openxr = "0.19"
@@ -97,6 +99,7 @@ xkbcommon-rs = "0.1.0"
# wayland
wayland-backend = { version = "0.3.7", optional = true, default-features = false }
wayland-scanner = { version = "0.31.4", optional = true }
cosmic-text = "0.14.2"
[dependencies.smithay]

View File

@@ -7,3 +7,4 @@ pub mod registry;
pub mod resource;
pub mod scenegraph;
pub mod task;
pub mod color;

View File

@@ -12,28 +12,25 @@ use crate::nodes::{audio, drawable, input};
use bevy::MinimalPlugins;
use bevy::a11y::AccessibilityPlugin;
use bevy::app::{App, MainScheduleOrder, ScheduleRunnerPlugin, TerminalCtrlCHandlerPlugin};
use bevy::app::{App, TerminalCtrlCHandlerPlugin};
use bevy::asset::{AssetMetaCheck, UnapprovedPathMode};
use bevy::audio::AudioPlugin;
use bevy::core_pipeline::CorePipelinePlugin;
use bevy::diagnostic::DiagnosticsPlugin;
use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel};
use bevy::gizmos::GizmoPlugin;
use bevy::gltf::GltfPlugin;
use bevy::input::{InputPlugin, InputSystem};
use bevy::input::InputPlugin;
use bevy::pbr::PbrPlugin;
use bevy::remote::RemotePlugin;
use bevy::remote::http::RemoteHttpPlugin;
use bevy::render::{RenderDebugFlags, RenderPlugin};
use bevy::scene::ScenePlugin;
use bevy::text::FontLoader;
use bevy::winit::{WakeUp, WinitPlugin};
use bevy_mod_meshtext::MeshTextPlugin;
use bevy_mod_openxr::action_binding::OxrActionBindingPlugin;
use bevy_mod_openxr::action_set_attaching::OxrActionAttachingPlugin;
use bevy_mod_openxr::action_set_syncing::OxrActionSyncingPlugin;
use bevy_mod_openxr::add_xr_plugins;
use bevy_mod_openxr::exts::OxrExtensions;
use bevy_mod_openxr::features::handtracking::HandTrackingPlugin;
use bevy_mod_openxr::features::overlay::OxrOverlaySettings;
use bevy_mod_openxr::features::passthrough::OxrPassthroughPlugin;
use bevy_mod_openxr::init::{OxrInitPlugin, should_run_frame_loop};
@@ -42,12 +39,13 @@ use bevy_mod_openxr::render::{OxrRenderPlugin, OxrWaitFrameSystem};
use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrSessionConfig};
use bevy_mod_openxr::types::AppInfo;
use bevy_mod_xr::hand_debug_gizmos::HandGizmosPlugin;
use bevy_mod_xr::session::{XrFirst, XrHandleEvents, session_running};
use bevy_mod_xr::session::{XrFirst, XrHandleEvents};
use clap::Parser;
use core::client::{Client, tick_internal_client};
use core::task;
use directories::ProjectDirs;
use nodes::drawable::model::ModelNodePlugin;
use nodes::drawable::text::TextNodePlugin;
use nodes::spatial::SpatialNodePlugin;
use objects::ServerObjects;
use objects::input::sk_controller::ControllerPlugin;
@@ -272,8 +270,7 @@ fn bevy_loop(
..default()
});
let mut plugins = MinimalPlugins
.build()
// .disable::<ScheduleRunnerPlugin>()
.build().add(DiagnosticsPlugin)
.add(TransformPlugin)
.add(InputPlugin)
/* .add(AccessibilityPlugin) */;
@@ -354,6 +351,7 @@ fn bevy_loop(
.disable::<OxrActionAttachingPlugin>()
.disable::<OxrActionSyncingPlugin>(),
);
// font size is in meters
app.add_plugins((
bevy_sk::hand::HandPlugin,
bevy_sk::vr_materials::SkMaterialPlugin {
@@ -363,7 +361,7 @@ fn bevy_loop(
));
app.add_plugins(HandGizmosPlugin);
// app.add_plugins(MeshTextPlugin);
// app.init_asset::<Font>().init_asset_loader::<FontLoader>();
app.init_asset::<Font>().init_asset_loader::<FontLoader>();
if let Some(priority) = args.overlay_priority {
app.insert_resource(OxrOverlaySettings {
session_layer_placement: priority,
@@ -388,6 +386,7 @@ fn bevy_loop(
app.add_plugins((
SpatialNodePlugin,
ModelNodePlugin,
TextNodePlugin,
PlaySpacePlugin,
HandPlugin,
ControllerPlugin,
@@ -401,6 +400,17 @@ fn bevy_loop(
.in_set(OxrWaitFrameSystem)
.in_set(XrHandleEvents::FrameLoop),
);
use std::process::Command;
let output = Command::new("fc-match")
.arg("-f")
.arg("%{file}")
.output()
.expect("Failed to run fc-match");
let font_path = String::from_utf8_lossy(&output.stdout);
info!("Default font path: {}", font_path);
app.run();
}

View File

@@ -23,7 +23,6 @@ use stereokit_rust::{sk::MainThreadToken, system::Renderer, tex::SHCubemap};
// #[instrument(level = "debug", skip(sk))]
pub fn draw(token: &MainThreadToken) {
lines::draw_all(token);
text::draw_all(token);
match QUEUED_SKYTEX.lock().take() {
Some(Some(skytex)) => {
if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) {

View File

@@ -1,6 +1,7 @@
use super::{MODEL_PART_ASPECT_ALIAS_INFO, MaterialParameter, ModelAspect, ModelPartAspect};
use crate::bail;
use crate::core::client::Client;
use crate::core::color::ColorConvert as _;
use crate::core::error::Result;
use crate::core::registry::Registry;
use crate::core::resource::get_resource_file;
@@ -27,9 +28,13 @@ impl Plugin for ModelNodePlugin {
fn build(&self, app: &mut App) {
let (tx, rx) = mpsc::unbounded_channel();
LOAD_MODEL.set(tx).unwrap();
app.init_resource::<MaterialRegistry>();
app.insert_resource(MpscReceiver(rx));
app.add_systems(Update, load_models);
app.add_systems(PostUpdate, (gen_model_parts, apply_materials).chain());
app.add_systems(
PostUpdate,
(gen_model_parts, apply_materials, update_visibillity).chain(),
);
}
}
@@ -38,6 +43,24 @@ struct MpscReceiver<T>(mpsc::UnboundedReceiver<T>);
#[derive(Component)]
struct ModelNode(Weak<Model>);
fn update_visibillity(mut cmds: Commands) {
for model in MODEL_REGISTRY.get_valid_contents().into_iter() {
let Some(entity) = model.bevy_scene_entity.get().copied() else {
continue;
};
match model.spatial.node().map(|n| n.enabled()).unwrap_or(false) {
true => {
cmds.entity(entity)
.insert_recursive::<Children>(Visibility::Visible);
}
false => {
cmds.entity(entity)
.insert_recursive::<Children>(Visibility::Hidden);
}
}
}
}
fn load_models(
asset_server: Res<AssetServer>,
mut cmds: Commands,
@@ -50,7 +73,7 @@ fn load_models(
.spawn((
SceneRoot(handle),
ModelNode(Arc::downgrade(&model)),
SpatialNode(Arc::downgrade(&model.space)),
SpatialNode(Arc::downgrade(&model.spatial)),
))
.id();
model.bevy_scene_entity.set(entity).unwrap();
@@ -59,7 +82,7 @@ fn load_models(
fn apply_materials(
mut query: Query<&mut MeshMaterial3d<PbrMaterial>>,
mut material_registry: Local<MaterialRegistry>,
mut material_registry: ResMut<MaterialRegistry>,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<PbrMaterial>>,
) -> bevy::prelude::Result {
@@ -69,7 +92,9 @@ fn apply_materials(
.filter_map(|p| p.parts.get())
.flatten()
{
let mut mesh_mat = query.get_mut(*model_part.mesh_entity.get().unwrap())?;
let Ok(mut mesh_mat) = query.get_mut(*model_part.mesh_entity.get().unwrap()) else {
continue;
};
if let Some(material) = model_part.pending_material_replacement.lock().take() {
let pbr_mat = material.to_pbr_mat(&asset_server);
let handle = material_registry.get_handle(pbr_mat, &mut materials);
@@ -128,8 +153,8 @@ fn gen_model_parts(
let parent_spatial = parent
.as_ref()
.map(|p| p.space.clone())
.unwrap_or_else(|| model.space.clone());
let client = model.space.node()?.get_client()?;
.unwrap_or_else(|| model.spatial.clone());
let client = model.spatial.node()?.get_client()?;
let (spatial, model_part) =
match model.pre_bound_parts.lock().iter().find(|v| v.path == path) {
None => {
@@ -341,16 +366,13 @@ impl MaterialParameter {
MaterialParameter::Vec3(_val) => {
// nothing uses a Vec3
}
MaterialParameter::Color(val) => {
let rgba = Srgba::new(val.c.r, val.c.g, val.c.b, val.a);
match parameter_name {
"color" => mat.color = rgba.into(),
"emission_factor" => mat.emission_factor = rgba.into(),
v => {
error!("unknown param_name ({v}) for color")
}
MaterialParameter::Color(color) => match parameter_name {
"color" => mat.color = color.to_bevy(),
"emission_factor" => mat.emission_factor = color.to_bevy(),
v => {
error!("unknown param_name ({v}) for color")
}
}
},
MaterialParameter::Texture(resource) => {
let Some(texture_path) =
get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")])
@@ -474,11 +496,11 @@ impl ModelPartAspect for ModelPart {
Ok(())
}
}
#[derive(Default)]
struct MaterialRegistry(FxHashMap<HashedPbrMaterial, Handle<PbrMaterial>>);
#[derive(Default, Resource)]
pub struct MaterialRegistry(FxHashMap<HashedPbrMaterial, Handle<PbrMaterial>>);
impl MaterialRegistry {
/// returns strong handle for PbrMaterial elminitating duplications
fn get_handle(
pub fn get_handle(
&mut self,
material: PbrMaterial,
materials: &mut ResMut<Assets<PbrMaterial>>,
@@ -500,7 +522,7 @@ impl MaterialRegistry {
}
pub struct Model {
space: Arc<Spatial>,
spatial: Arc<Spatial>,
_resource_id: ResourceID,
bevy_scene_entity: OnceLock<Entity>,
parts: OnceLock<Vec<Arc<ModelPart>>>,
@@ -516,7 +538,7 @@ impl Model {
.ok_or_else(|| eyre!("Resource not found"))?;
let model = Arc::new(Model {
space: node.get_aspect::<Spatial>().unwrap().clone(),
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
_resource_id: resource_id,
bevy_scene_entity: OnceLock::new(),
pre_bound_parts: Mutex::default(),
@@ -553,10 +575,14 @@ impl Model {
}
None => {
// TODO: this could be a denail of service vector
let client = self.space.node().unwrap().get_client().unwrap();
let client = self.spatial.node().unwrap().get_client().unwrap();
let part_node = client.scenegraph.add_node(Node::generate(&client, false));
let spatial =
Spatial::add_to(&part_node, Some(self.space.clone()), Mat4::IDENTITY, false);
let spatial = Spatial::add_to(
&part_node,
Some(self.spatial.clone()),
Mat4::IDENTITY,
false,
);
let part = part_node.add_aspect(ModelPart {
entity: OnceLock::new(),
mesh_entity: OnceLock::new(),

View File

@@ -1,48 +1,223 @@
use crate::{
core::{
client::Client, destroy_queue, error::Result, registry::Registry,
client::Client, color::ColorConvert, error::Result, registry::Registry,
resource::get_resource_file,
},
nodes::{Node, spatial::Spatial},
nodes::{
Node,
drawable::XAlign,
spatial::{Spatial, SpatialNode},
},
};
use bevy::{platform::collections::HashMap, prelude::*};
use bevy_mesh_text_3d::{
Align, Attrs, MeshTextPlugin, Settings as FontSettings, generate_meshes,
text_glyphs::TextGlyphs,
};
use bevy_sk::vr_materials::PbrMaterial;
use color_eyre::eyre::eyre;
use glam::{Mat4, Vec2, vec3};
use core::f32;
use cosmic_text::Metrics;
use parking_lot::Mutex;
use std::{
ffi::OsStr,
mem,
path::PathBuf,
sync::{Arc, OnceLock},
};
use stereokit_rust::{
font::Font,
sk::MainThreadToken,
system::{TextAlign, TextFit, TextStyle as SkTextStyle},
util::{Color32, Color128},
};
use tokio::sync::mpsc;
use super::{TextAspect, TextStyle};
static SPAWN_TEXT: OnceLock<mpsc::UnboundedSender<Arc<Text>>> = OnceLock::new();
static TEXT_REGISTRY: Registry<Text> = Registry::new();
#[derive(Resource)]
struct MpscReceiver<T>(mpsc::UnboundedReceiver<T>);
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,
pub struct TextNodePlugin;
impl Plugin for TextNodePlugin {
fn build(&self, app: &mut App) {
// Text init stuff
// app.init_asset::<Font>().init_asset_loader::<FontLoader>();
// load_internal_binary_asset!(
// app,
// Handle::default(),
// "assets/FiraMono-subset.ttf",
// |bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() }
// );
// 1.0 for font size in meters
app.add_plugins(MeshTextPlugin::new(1.0));
app.world_mut()
.resource_mut::<FontSettings>()
.font_system
.db_mut()
.load_system_fonts();
let (tx, rx) = mpsc::unbounded_channel();
SPAWN_TEXT.set(tx).unwrap();
app.init_resource::<MaterialRegistry>();
app.insert_resource(MpscReceiver(rx));
app.add_systems(Update, (spawn_text, update_visibillity).chain());
}
}
pub struct Text {
space: Arc<Spatial>,
font_path: Option<PathBuf>,
style: OnceLock<SkTextStyle>,
fn update_visibillity(mut cmds: Commands) {
for text in TEXT_REGISTRY.get_valid_contents().into_iter() {
let Some(entity) = text.entity.lock().as_ref().copied() else {
continue;
};
match text.spatial.node().map(|n| n.enabled()).unwrap_or(false) {
true => {
cmds.entity(entity)
.insert_recursive::<Children>(Visibility::Visible);
}
false => {
cmds.entity(entity)
.insert_recursive::<Children>(Visibility::Hidden);
}
}
}
}
fn spawn_text(
mut mpsc: ResMut<MpscReceiver<Arc<Text>>>,
mut cmds: Commands,
mut font_settings: ResMut<FontSettings>,
mut material_registry: ResMut<MaterialRegistry>,
mut materials: ResMut<Assets<PbrMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
mut font_registry: Local<FontDatabaseRegistry>,
) {
while let Ok(text) = mpsc.0.try_recv() {
if let Some(entity) = text.entity.lock().take() {
cmds.entity(entity).despawn();
}
let style = text.data.lock();
let old_db = text.font_path.clone().map(|p| {
let db = font_registry.get(p);
mem::swap(font_settings.font_system.db_mut(), db);
db
});
let attrs = Attrs::new().weight(cosmic_text::Weight::BOLD);
let alignment = Some(match style.text_align_x {
super::XAlign::Left => Align::Right,
super::XAlign::Center => Align::Center,
super::XAlign::Right => Align::Left,
});
let text_string = text.text.lock().clone();
let mut text_glyphs = TextGlyphs::new(
Metrics {
font_size: style.character_height,
line_height: style.character_height,
},
[(text_string.as_str(), attrs.clone())],
&attrs,
&mut font_settings.font_system,
alignment,
);
let max_width = style.bounds.as_ref().map(|v| v.bounds.x);
let max_height = style.bounds.as_ref().map(|v| v.bounds.x);
let (width, height) =
text_glyphs.measure(max_width, max_height, &mut font_settings.font_system);
info!(width, height, ?style.text_align_x);
let meshes = generate_meshes(
bevy_mesh_text_3d::InputText::Simple {
text: text_string,
material: material_registry.get_handle(
PbrMaterial {
color: style.color.to_bevy(),
emission_factor: Color::WHITE,
metallic: 0.0,
roughness: 1.0,
// If alpha is supported on text we need to change this
alpha_mode: AlphaMode::Opaque,
double_sided: false,
..default()
},
&mut materials,
),
attrs,
},
&mut font_settings,
bevy_mesh_text_3d::Parameters {
extrusion_depth: 0.0,
font_size: style.character_height,
line_height: style.character_height,
alignment,
max_width,
max_height,
},
&mut meshes,
);
if let Some(db) = old_db {
mem::swap(font_settings.font_system.db_mut(), db);
}
let Ok(meshes) = meshes.inspect_err(|err| error!("unable to create text meshes: {err}"))
else {
continue;
};
let dist = meshes.iter().fold(f32::MAX, |dist, v| {
dist.min(v.transform.translation.x)
// if dist > v.transform.translation.x {
// v.transform.translation.x
// } else {
// dist
// }
});
// TODO: text align
let letters = meshes
.into_iter()
.map(|v| {
// info!("{:?}", v.transform);
cmds.spawn((
Mesh3d(v.mesh),
MeshMaterial3d(v.material),
// rotation is sus, might be related to the gltf coordinate system
Transform::from_rotation(Quat::from_rotation_y(f32::consts::PI))
* Transform::from_xyz(
-dist
+ match style.bounds.as_ref().map(|v| v.anchor_align_x) {
Some(XAlign::Center) => width * -0.5,
Some(XAlign::Right) => width * -1.0,
Some(XAlign::Left) => 0.0,
None => 0.0,
},
0.0,
0.0,
) * v.transform,
))
.id()
})
.collect::<Vec<_>>();
let entity = cmds
.spawn((SpatialNode(Arc::downgrade(&text.spatial)),))
.add_children(&letters)
.id();
text.entity.lock().replace(entity);
}
}
#[derive(Default)]
struct FontDatabaseRegistry(HashMap<PathBuf, cosmic_text::fontdb::Database>);
impl FontDatabaseRegistry {
fn get(&mut self, path: PathBuf) -> &mut cosmic_text::fontdb::Database {
self.0.entry(path).or_insert_with_key(|path| {
let mut db = cosmic_text::fontdb::Database::new();
if let Err(err) = db.load_font_file(path) {
error!("unable to load font file {} {err}", path.to_string_lossy());
};
db
})
}
}
use super::{TextAspect, TextStyle, model::MaterialRegistry};
static TEXT_REGISTRY: Registry<Text> = Registry::new();
pub struct Text {
spatial: Arc<Spatial>,
font_path: Option<PathBuf>,
entity: Mutex<Option<Entity>>,
text: Mutex<String>,
data: Mutex<TextStyle>,
}
@@ -50,88 +225,20 @@ impl Text {
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
let text = TEXT_REGISTRY.add(Text {
space: node.get_aspect::<Spatial>().unwrap().clone(),
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
font_path: style.font.as_ref().and_then(|res| {
get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
}),
style: OnceLock::new(),
entity: Mutex::new(None),
text: Mutex::new(text),
data: Mutex::new(style),
});
node.add_aspect_raw(text.clone());
_ = SPAWN_TEXT.get().unwrap().send(text.clone());
Ok(text)
}
fn draw(&self, token: &MainThreadToken) {
let style = self.style.get_or_init(|| {
let font = self
.font_path
.as_deref()
.and_then(|path| Font::from_file(path).ok())
.unwrap_or_default();
SkTextStyle::from_font(font, 1.0, Color32::WHITE)
});
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(
@@ -141,30 +248,19 @@ impl TextAspect for Text {
) -> Result<()> {
let this_text = node.get_aspect::<Text>()?;
this_text.data.lock().character_height = height;
_ = SPAWN_TEXT.get().unwrap().send(this_text.clone());
Ok(())
}
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
let this_text = node.get_aspect::<Text>()?;
*this_text.text.lock() = text;
_ = SPAWN_TEXT.get().unwrap().send(this_text.clone());
Ok(())
}
}
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);
}
}
}
}

View File

@@ -8,6 +8,7 @@ mod tip;
pub use handler::*;
pub use method::*;
use tracing::debug_span;
use super::Aspect;
use super::AspectIdentifier;
@@ -120,6 +121,7 @@ pub fn process_input() {
node.enabled()
});
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
let _span = debug_span!("handle input handler").entered();
for method_alias in handler.method_aliases.get_aliases() {
method_alias.set_enabled(false);
}
@@ -136,6 +138,7 @@ pub fn process_input() {
}
};
let ser_span = debug_span!("serializing input").entered();
let (methods, datas) = methods
.clone()
// filter out methods without the handler in their handler order
@@ -161,7 +164,9 @@ pub fn process_input() {
// serialize the data
.map(|(a, m)| (a.clone(), m.serialize(a.get_id(), &handler)))
.unzip::<_, _, Vec<_>, Vec<_>>();
drop(ser_span);
let _span = debug_span!("client input").entered();
let _ = input_handler_client::input(&handler_node, &methods, &datas);
}
for method in methods {

View File

@@ -9,7 +9,6 @@ use crate::core::client::Client;
use crate::core::error::Result;
use crate::core::registry::Registry;
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
use bevy::ecs::entity_disabling::Disabled;
use bevy::prelude::Transform as BevyTransform;
use bevy::prelude::*;
use color_eyre::eyre::OptionExt;
@@ -18,7 +17,6 @@ use mint::Vector3;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::fmt::Debug;
use std::sync::atomic::Ordering;
use std::sync::{Arc, OnceLock, Weak};
use std::{f32, ptr};
use stereokit_rust::maths::Bounds;
@@ -28,62 +26,39 @@ impl Plugin for SpatialNodePlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PostUpdate,
(replace_childof, update_spatial_nodes).before(TransformSystem::TransformPropagate),
update_spatial_nodes.before(TransformSystem::TransformPropagate),
);
}
}
fn update_spatial_nodes(
mut query: Query<(Entity, &mut BevyTransform, &SpatialNode, Has<Disabled>)>,
cmds: ParallelCommands,
mut query: Query<(&mut BevyTransform, &SpatialNode, Option<&ChildOf>)>,
parent_query: Query<&GlobalTransform>,
) {
query
.par_iter_mut()
.for_each(|(entity, mut transform, spatial_node, disabled)| {
.for_each(|(mut transform, spatial_node, child_of)| {
let Some(spatial) = spatial_node.0.upgrade() else {
// should we despawn the entity?
return;
};
if !spatial
.node()
.map(|n| n.enabled.load(Ordering::Relaxed))
.unwrap_or(true)
{
// cmds.command_scope(|mut cmds| {
// cmds.entity(entity).insert(Visibility::Hidden);
// });
// return;
}
let mat4 = spatial.global_transform();
let scale_zero = mat4.to_scale_rotation_translation().0.length_squared() > 0.0;
if scale_zero {
// cmds.command_scope(|mut cmds| {
// cmds.entity(entity).insert(Visibility::Hidden);
// });
} else if disabled {
// cmds.command_scope(|mut cmds| {
// cmds.entity(entity).insert(Visibility::Visible);
// });
match child_of {
Some(child_of) => {
let Ok(parent) = parent_query.get(child_of.0) else {
warn!("SpatialNode bevy Parent doesn't have global transform");
return;
};
*transform =
BevyTransform::from_matrix(parent.compute_matrix().inverse() * mat4);
}
None => {
*transform = BevyTransform::from_matrix(mat4);
}
}
*transform = BevyTransform::from_matrix(mat4);
});
}
fn replace_childof(query: Query<(Entity, &ChildOf), With<SpatialNode>>, mut cmds: Commands) {
for (entity, parent) in &query {
cmds.entity(entity)
.insert(NonSpatialChildOf(parent.0))
.remove::<ChildOf>();
}
}
#[derive(Component, Default, Debug, PartialEq, Eq)]
#[relationship_target(relationship = NonSpatialChildOf, linked_spawn)]
pub struct NonSpatialChildren(Vec<Entity>);
#[derive(Component, Debug, PartialEq, Eq)]
#[relationship(relationship_target = NonSpatialChildren)]
pub struct NonSpatialChildOf(Entity);
#[derive(Clone, Component, Debug)]
#[require(BevyTransform)]
pub struct SpatialNode(pub Weak<Spatial>);

View File

@@ -154,9 +154,7 @@ fn update(
ref_space: Option<Res<XrPrimaryReferenceSpace>>,
state: Option<Res<OxrFrameState>>,
) {
info!("calling update");
let (Some(session), Some(state), Some(ref_space)) = (session, state, ref_space) else {
info!("something is missing");
controllers.left.set_enabled(false);
controllers.right.set_enabled(false);
return;
@@ -288,6 +286,7 @@ impl SkController {
let tracked = Tracked::new(connection, &path);
let tip = InputDataType::Tip(Tip::default());
let node = spatial.node().unwrap();
node.set_enabled(false);
let model = Model::add_to(&node, ResourceID::Direct(CURSOR_MODEL_PATH.into())).unwrap();
let model_part = model.get_model_part("Cursor".to_string()).unwrap();
let input = InputMethod::add_to(
@@ -324,7 +323,6 @@ impl SkController {
ref_space: XrReferenceSpace,
) {
let Some(space) = self.space.as_ref() else {
info!("no space 3:");
return;
};
let Ok(location) = session
@@ -339,10 +337,8 @@ impl SkController {
| SpaceLocationFlags::ORIENTATION_VALID
| SpaceLocationFlags::ORIENTATION_TRACKED,
);
info!("{:#?}", location.location_flags);
self.set_enabled(enabled);
if enabled {
info!("update");
let world_transform = Mat4::from(Affine3A::from(location.pose.to_xr_pose()));
self.model_part
.set_material_parameter("roughness".to_string(), MaterialParameter::Float(1.0));