From a832266989b52d5bb9f50d458cbe787e6fd74210 Mon Sep 17 00:00:00 2001 From: Schmarni Date: Wed, 15 Jan 2025 21:18:31 +0100 Subject: [PATCH] refactor: add text support Signed-off-by: Schmarni --- Cargo.toml | 15 +- src/FiraMono-subset.ttf | Bin 0 -> 18848 bytes src/main.rs | 20 ++- src/nodes/drawable/lines.rs | 3 +- src/nodes/drawable/model.rs | 67 +++++++-- src/nodes/drawable/text.rs | 268 ++++++++--------------------------- src/nodes/mod.rs | 5 +- src/objects/input/sk_hand.rs | 2 - 8 files changed, 141 insertions(+), 239 deletions(-) create mode 100644 src/FiraMono-subset.ttf diff --git a/Cargo.toml b/Cargo.toml index b38c533..33657d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,10 +51,16 @@ auto_link_exclude_list = [ ] [dependencies] -bevy = { version = "0.15", features = ["wayland", "mp3", "wav", "trace_tracy"] } -bevy_mod_xr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "0.15rc" } -bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "0.15rc" } -bevy_xr_utils = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "0.15rc" } +bevy = { version = "0.15.1", features = [ + "wayland", + "mp3", + "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" winit = { version = "0.30", default-features = false, features = [] } # small utility thingys @@ -97,6 +103,7 @@ directories = "5.0.1" xkbcommon-rs = "0.1.0" thiserror = "2.0.9" crossbeam-channel = "0.5.14" +atomicow = "1.0.0" # wayland # wayland-scanner = "0.31.2" diff --git a/src/FiraMono-subset.ttf b/src/FiraMono-subset.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6989c2e4835006627b2daf6660f65b40cfa5edc4 GIT binary patch literal 18848 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 K|J8p_=>Gr=x%t5W literal 0 HcmV?d00001 diff --git a/src/main.rs b/src/main.rs index 772dbc1..6fbac34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,13 +17,14 @@ use bevy::app::{ App, AppExit, PluginGroup, PluginsState, PostUpdate, ScheduleRunnerPlugin, Startup, 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::color::Color; use bevy::core_pipeline::{CorePipelinePlugin, Skybox}; use bevy::gizmos::GizmoPlugin; use bevy::gltf::GltfPlugin; use bevy::image::Image; +use bevy::input::InputPlugin; use bevy::log::LogPlugin; use bevy::pbr::{PbrPlugin, StandardMaterial}; use bevy::prelude::{ @@ -34,11 +35,13 @@ use bevy::prelude::{ use bevy::render::pipelined_rendering::PipelinedRenderingPlugin; use bevy::render::RenderPlugin; use bevy::scene::ScenePlugin; +use bevy::text::{Font, FontLoader, TextPlugin}; use bevy::time::Time; use bevy::utils::default; use bevy::window::WindowPlugin; use bevy::winit::{EventLoopProxyWrapper, WakeUp, WinitPlugin}; use bevy::{DefaultPlugins, MinimalPlugins}; +use bevy_mod_meshtext::MeshTextPlugin; use bevy_mod_openxr::action_set_syncing::{OxrActionSyncingPlugin, OxrSyncActionSet}; use bevy_mod_openxr::exts::OxrExtensions; use bevy_mod_openxr::features::overlay::{OxrOverlaySessionEvent, OxrOverlaySettings}; @@ -267,6 +270,7 @@ fn bevy_loop( .disable::() .add(TransformPlugin) .add(HierarchyPlugin) + .add(InputPlugin) .add(AccessibilityPlugin); base = match headless { true => { @@ -343,15 +347,25 @@ fn bevy_loop( bevy_app.add_event::(); bevy_app.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin); } + bevy_app + .init_asset::() + .init_asset_loader::(); + bevy_app.add_plugins(MeshTextPlugin); bevy_app.add_plugins(StardustBevyPlugin); bevy_app.add_plugins(( BevyLinesPlugin, StardustModelPlugin, StardustHandPlugin, - // StardustTextPlugin, + StardustTextPlugin, StardustSoundPlugin, 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)] struct SkyTexture(Handle); // Skytex/light stuff @@ -482,7 +496,7 @@ fn bevy_loop( .flatten() .map(|mut waiter| { TOKIO.spawn_blocking(move || { - let _span = debug_span!("eeping").entered(); + let _span = debug_span!("frame eeping").entered(); let result = waiter.wait(); (waiter, result) }) diff --git a/src/nodes/drawable/lines.rs b/src/nodes/drawable/lines.rs index 9e1800a..78574fe 100644 --- a/src/nodes/drawable/lines.rs +++ b/src/nodes/drawable/lines.rs @@ -162,8 +162,9 @@ pub fn draw_all( mut meshes: ResMut>, mut materials: ResMut>, mut cmds: Commands, - hmd: Single<&GlobalTransform, With>, + hmd: Option>>, ) { + let Some(hmd) = hmd else { return }; let material = StandardMaterial { base_color: Color::WHITE, alpha_mode: AlphaMode::Blend, diff --git a/src/nodes/drawable/model.rs b/src/nodes/drawable/model.rs index 362e4dd..df72f23 100644 --- a/src/nodes/drawable/model.rs +++ b/src/nodes/drawable/model.rs @@ -9,14 +9,15 @@ use crate::nodes::spatial::Spatial; use crate::nodes::Node; use crate::DefaultMaterial; 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::core::Name; use bevy::gltf::GltfAssetLabel; +use bevy::image::Image; use bevy::math::bounding::Aabb3d; use bevy::pbr::MeshMaterial3d; 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, }; use bevy::reflect::GetField; @@ -42,6 +43,7 @@ impl MaterialParameter { client: &Client, material: &mut DefaultMaterial, parameter_name: &str, + asset_server: &AssetServer, ) { match self { MaterialParameter::Bool(val) => match parameter_name { @@ -58,7 +60,7 @@ impl MaterialParameter { if let Some(field) = material.get_field_mut::(name) { *field = *val; } 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::(name) { *field = *val; } else { - warn!("unknown bool material parameter name: {name}"); + warn!("unknown u32 material parameter name: {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 => { if let Some(field) = material.get_field_mut::(name) { *field = *val; } 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::(name) { *field = (*val).into(); } 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::(name) { *field = (*val).into(); } else { - warn!("unknown bool material parameter name: {name}"); + warn!("unknown vec3 material parameter name: {name}"); } } }, @@ -102,25 +111,46 @@ impl MaterialParameter { "color" => { 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 => { if let Some(field) = material.get_field_mut::(name) { *field = LinearRgba::new(val.c.r, val.c.g, val.c.b, val.a).into(); } else { - warn!("unknown bool material parameter name: {name}"); + warn!("unknown color material parameter name: {name}"); } } }, MaterialParameter::Texture(resource) => { - match parameter_name { - name => { - warn!("unknown texture material parameter name: {name}"); - } - } let Some(texture_path) = get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")]) else { return; }; + let image = asset_server.load::(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::>>(name) { + field.replace(image); + } else { + warn!("unknown texture material parameter name: {name}"); + } + } + } error!("TODO: implement texture changing"); } } @@ -193,16 +223,18 @@ fn update_model_parts( mut part_query: Query<( &mut Transform, &mut MeshMaterial3d, + &mut Visibility, Has, )>, mut cmds: Commands, + asset_server: Res, ) { for model in &models { let Some(model) = model.0.upgrade() else { continue; }; 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 .get() .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(); } *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 'mat_params: { @@ -231,6 +269,7 @@ fn update_model_parts( &client, &mut new_material, ¶meter_name, + &asset_server, ); } mat.0 = mats.add(new_material); diff --git a/src/nodes/drawable/text.rs b/src/nodes/drawable/text.rs index 9bd75d8..f46c1f9 100644 --- a/src/nodes/drawable/text.rs +++ b/src/nodes/drawable/text.rs @@ -1,61 +1,50 @@ use crate::{ - bevy_plugin::convert_linear_rgba, + bevy_plugin::{convert_linear_rgba, DESTROY_ENTITY}, core::{ client::Client, - destroy_queue, error::{Result, ServerError}, registry::Registry, resource::get_resource_file, }, - nodes::{spatial::Spatial, Aspect, Node}, + nodes::{spatial::Spatial, Node}, DefaultMaterial, }; use bevy::{ app::{App, Plugin, PostUpdate, PreUpdate}, - asset::{AssetServer, Assets, RenderAssetUsages}, - color::Color, - image::Image, + asset::{AssetServer, Assets}, 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}, + prelude::{Commands, Deref, Entity, Query, Res, ResMut, Resource, Transform}, }; -use glam::{vec3, Mat4, Vec2, Vec3}; +use bevy_mod_meshtext::{HorizontalLayout, MeshText, MeshTextFont, VerticalLayout}; use once_cell::sync::OnceCell; use parking_lot::Mutex; use std::{ffi::OsStr, path::PathBuf, sync::Arc}; -use tracing::{info, info_span}; +use tracing::info_span; use super::{TextAspect, TextStyle}; static TEXT_REGISTRY: Registry = Registry::new(); -// 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, -// } -// } +const fn convert_align_x(x_align: super::XAlign) -> HorizontalLayout { + match x_align { + super::XAlign::Left => HorizontalLayout::Left, + super::XAlign::Center => HorizontalLayout::Centered, + super::XAlign::Right => HorizontalLayout::Right, + } +} +const fn convert_align_y(y_align: super::YAlign) -> VerticalLayout { + match y_align { + super::YAlign::Top => VerticalLayout::Top, + super::YAlign::Center => VerticalLayout::Centered, + super::YAlign::Bottom => VerticalLayout::Bottom, + } +} pub struct StardustTextPlugin; impl Plugin for StardustTextPlugin { fn build(&self, app: &mut App) { let (tx, rx) = crossbeam_channel::unbounded(); - SPAWN_TEXT_SENDER.set(tx); + _ = SPAWN_TEXT_SENDER.set(tx); app.insert_resource(SpawnTextReader(rx)); app.add_systems(PostUpdate, update_text); app.add_systems(PreUpdate, spawn_text); @@ -65,7 +54,7 @@ impl Plugin for StardustTextPlugin { fn update_text(mut surface_query: Query<(&mut Transform)>) { for text in TEXT_REGISTRY.get_valid_contents() { let Some((mut transform)) = text - .surface + .entity .get() .and_then(|v| surface_query.get_mut(*v).ok()) else { @@ -73,21 +62,13 @@ fn update_text(mut surface_query: Query<(&mut Transform)>) { }; // 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, - // )), - ); + *transform = Transform::from_matrix(text.space.global_transform()); } } fn spawn_text( reader: Res, mut cmds: Commands, - mut images: ResMut>, - mut meshes: ResMut>, mut mats: ResMut>, asset_server: Res, ) { @@ -96,99 +77,36 @@ fn spawn_text( let _span2 = info_span!("text data lock").entered(); let data = text.data.lock(); drop(_span2); - 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() - }; - - 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 _span2 = info_span!("text str lock").entered(); + let str = text.text.lock().clone(); + drop(_span2); + let mat = mats.add(DefaultMaterial { + base_color: convert_linear_rgba(data.color).into(), + unlit: true, + ..Default::default() + }); let font = text .font_path .as_ref() - .map(|v| asset_server.load(v.as_path())); - drop(_span); + .map(|p| asset_server.load(p.as_path())); + 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(); - text.cam_entity.set(cam); - text.ui_root.set(ui_root); - text.surface.set(surface); + text.entity.set(entity); } } static SPAWN_TEXT_SENDER: OnceCell>> = OnceCell::new(); @@ -198,11 +116,9 @@ struct SpawnTextReader(crossbeam_channel::Receiver>); pub struct Text { space: Arc, font_path: Option, - text: Mutex, + text: Mutex>, data: Mutex, - cam_entity: OnceCell, - ui_root: OnceCell, - surface: OnceCell, + entity: OnceCell, } impl Text { pub fn add_to(node: &Arc, text: String, style: TextStyle) -> Result> { @@ -212,11 +128,9 @@ impl Text { font_path: style.font.as_ref().and_then(|res| { get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")]) }), - text: Mutex::new(text), + text: Mutex::new(text.into()), data: Mutex::new(style), - ui_root: OnceCell::new(), - cam_entity: OnceCell::new(), - surface: OnceCell::new(), + entity: OnceCell::new(), }); node.add_aspect_raw(text.clone()); if let Some(sender) = SPAWN_TEXT_SENDER.get() { @@ -225,79 +139,6 @@ impl Text { 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, - // ); - // } - // } - // } } impl TextAspect for Text { fn set_character_height( @@ -312,12 +153,15 @@ impl TextAspect for Text { fn set_text(node: Arc, _calling_client: Arc, text: String) -> Result<()> { let this_text = node.get_aspect::()?; - *this_text.text.lock() = text; + *this_text.text.lock() = text.into(); Ok(()) } } impl Drop for Text { fn drop(&mut self) { + if let Some(e) = self.entity.get() { + DESTROY_ENTITY.send(*e); + } TEXT_REGISTRY.remove(self); } } diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index 1dca709..36e70a6 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -13,7 +13,6 @@ use crate::core::error::{Result, ServerError}; use crate::core::queued_mutex::QueuedMutex; use crate::core::registry::Registry; use crate::core::scenegraph::MethodResponseSender; -use parking_lot::Mutex; use portable_atomic::{AtomicBool, Ordering}; use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; @@ -21,12 +20,12 @@ use spatial::Spatial; use stardust_xr::messenger::MessageSenderHandle; use stardust_xr::scenegraph::ScenegraphError; use stardust_xr::schemas::flex::{deserialize, serialize}; +use tracing::debug_span; use std::any::{Any, TypeId}; use std::fmt::Debug; use std::os::fd::OwnedFd; use std::sync::{Arc, Weak}; use std::vec::Vec; -use tracing::{debug_span, info}; #[derive(Default)] pub struct Message { @@ -345,7 +344,7 @@ impl Aspects { } fn get(&self) -> Result> { self.0 - .lock() + .read_now() .get(&A::ID) .cloned() .map(|a| a.as_any()) diff --git a/src/objects/input/sk_hand.rs b/src/objects/input/sk_hand.rs index c770fb0..2a6f2f3 100644 --- a/src/objects/input/sk_hand.rs +++ b/src/objects/input/sk_hand.rs @@ -165,7 +165,6 @@ fn update_hands( const PINCH_MAX: f32 = 0.11; 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 fn pinch_activation(joints: &[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]) -> f32 { 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_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 fn grip_activation(joints: &[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]) -> f32 { let combined_radius = joints[HandBone::RingTip as usize].radius