WIP: Porting the server to Bevy #31
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -3,7 +3,7 @@ name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
build_and_package:
|
||||
@@ -11,13 +11,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install runtime dependencies
|
||||
run: sudo apt install -y --no-install-recommends libxkbcommon-dev libstdc++6 libopenxr-dev libx11-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev libgbm-dev libfontconfig-dev libjsoncpp-dev libxcb1-dev libglx-dev libxcb-glx0-dev libdrm-dev libwayland-dev libfreetype-dev libpng-dev
|
||||
|
||||
- name: Install build dependencies
|
||||
run: sudo apt install -y --no-install-recommends cmake ninja-build
|
||||
run: sudo apt install -y --no-install-recommends cmake ninja-build libfuse2
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -27,12 +27,11 @@ jobs:
|
||||
- name: Build server
|
||||
run: cargo build --release
|
||||
|
||||
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage -O /usr/local/bin/appimagetool; \
|
||||
chmod +x /usr/local/bin/appimagetool; \
|
||||
sed -i 's|AI\x02|\x00\x00\x00|' /usr/local/bin/appimagetool
|
||||
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage -O /usr/local/bin/appimagetool; \
|
||||
chmod +x /usr/local/bin/appimagetool; \
|
||||
sed -i 's|AI\x02|\x00\x00\x00|' /usr/local/bin/appimagetool
|
||||
- name: Install cargo-appimage
|
||||
run: cargo install cargo-appimage
|
||||
|
||||
@@ -40,7 +39,7 @@ jobs:
|
||||
run: cargo appimage
|
||||
|
||||
- name: Upload AppImage
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: '*.AppImage'
|
||||
path: "target/appimage/stardust-xr-server.AppImage"
|
||||
|
||||
4432
Cargo.lock
generated
4432
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
73
Cargo.toml
73
Cargo.toml
@@ -9,22 +9,35 @@ license = "GPLv2"
|
||||
repository = "https://github.com/StardustXR/stardust-xr-server/"
|
||||
homepage = "https://stardustxr.org"
|
||||
|
||||
[patch.crates-io]
|
||||
tracing-tracy = { git = "https://github.com/nagisa/rust_tracy_client", tag = "tracy-client-v0.17.6" }
|
||||
tracy-client = { git = "https://github.com/nagisa/rust_tracy_client", tag = "tracy-client-v0.17.6" }
|
||||
tracy-client-sys = { git = "https://github.com/nagisa/rust_tracy_client", tag = "tracy-client-v0.17.6" }
|
||||
|
||||
[workspace]
|
||||
members = ["codegen"]
|
||||
|
||||
[workspace.dependencies.stardust-xr]
|
||||
git = "https://github.com/StardustXR/core.git"
|
||||
branch = "dev"
|
||||
|
||||
[[bin]]
|
||||
name = "stardust-xr-server"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["wayland"]
|
||||
default = []
|
||||
wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
|
||||
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
|
||||
profile_app = ["dep:tracing-tracy"]
|
||||
local_deps = ["stereokit-rust/force-local-deps"]
|
||||
|
||||
# Enable a small amount of optimization in the dev profile.
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
# Enable a large amount of optimization in the dev profile for dependencies.
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[package.metadata.appimage]
|
||||
auto_link = true
|
||||
@@ -37,21 +50,24 @@ auto_link_exclude_list = [
|
||||
"libEGL*",
|
||||
]
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
strip = "none"
|
||||
debug-assertions = true
|
||||
overflow-checks = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = "line-tables-only"
|
||||
strip = "none"
|
||||
debug-assertions = true
|
||||
overflow-checks = false
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.15.1", features = [
|
||||
"wayland",
|
||||
"mp3",
|
||||
"wav",
|
||||
"trace_tracy",
|
||||
] }
|
||||
bevy_mod_xr.git = "https://github.com/awtterpip/bevy_oxr"
|
||||
bevy_mod_openxr.git = "https://github.com/awtterpip/bevy_oxr"
|
||||
bevy_xr_utils.git = "https://github.com/awtterpip/bevy_oxr"
|
||||
# bevy_mod_xr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "pbr" }
|
||||
# bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "pbr" }
|
||||
# bevy_xr_utils = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "pbr" }
|
||||
bevy_mod_meshtext.git = "https://github.com/Schmarni-Dev/bevy_mod_meshtext"
|
||||
# bevy_sk.path = "../bevy_sk"
|
||||
bevy_sk.git = "https://github.com/MalekiRe/bevy_sk"
|
||||
openxr = "0.19"
|
||||
winit = { version = "0.30", default-features = false, features = [] }
|
||||
# small utility thingys
|
||||
once_cell = "1.19.0"
|
||||
nanoid = "0.4.0"
|
||||
@@ -87,13 +103,16 @@ prisma = "0.1.1"
|
||||
libc = "0.2.155"
|
||||
nix = "0.29.0"
|
||||
input-event-codes = "6.2.0"
|
||||
zbus = { version = "4.4.0", default-features = false, features = ["tokio"] }
|
||||
zbus = { version = "5.0.0", default-features = false, features = ["tokio"] }
|
||||
directories = "5.0.1"
|
||||
xkbcommon-rs = "0.1.0"
|
||||
thiserror = "2.0.9"
|
||||
crossbeam-channel = "0.5.14"
|
||||
atomicow = "1.0.0"
|
||||
|
||||
# wayland
|
||||
wayland-backend = { version = "0.3.7", optional = true, default-features = false }
|
||||
wayland-scanner = { version = "0.31.4", optional = true }
|
||||
wayland-scanner = { version = "0.31.2", optional = true }
|
||||
wayland-backend = { version = "0.3.4", optional = true }
|
||||
|
||||
[dependencies.smithay]
|
||||
# git = "https://github.com/technobaboo/smithay.git"
|
||||
@@ -101,17 +120,15 @@ wayland-scanner = { version = "0.31.4", optional = true }
|
||||
git = "https://github.com/smithay/smithay.git"
|
||||
# path = "../smithay"
|
||||
default-features = false
|
||||
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
|
||||
features = [
|
||||
"desktop",
|
||||
"backend_drm",
|
||||
"backend_egl",
|
||||
"renderer_gl",
|
||||
"wayland_frontend",
|
||||
]
|
||||
optional = true
|
||||
|
||||
|
||||
[dependencies.stereokit-rust]
|
||||
# path = "../StereoKit-rust"
|
||||
git = "https://github.com/mvvvv/StereoKit-rust.git"
|
||||
# git = "https://github.com/technobaboo/StereoKit-rust.git"
|
||||
features = ["no-event-loop"]
|
||||
default-features = false
|
||||
|
||||
[dependencies.stardust-xr]
|
||||
workspace = true
|
||||
|
||||
|
||||
@@ -25,10 +25,6 @@ pub fn codegen_field_protocol(_input: proc_macro::TokenStream) -> proc_macro::To
|
||||
codegen_protocol(FIELD_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_data_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(DATA_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(AUDIO_PROTOCOL)
|
||||
}
|
||||
@@ -53,6 +49,93 @@ pub fn codegen_item_panel_protocol(_input: proc_macro::TokenStream) -> proc_macr
|
||||
codegen_protocol(ITEM_PANEL_PROTOCOL)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn codegen_id_to_name_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let mut aspect_map: Vec<(u64, String)> = Vec::new();
|
||||
let mut method_map: Vec<(u64, String)> = Vec::new();
|
||||
let mut signal_map: Vec<(u64, String)> = Vec::new();
|
||||
let protocols = [
|
||||
ROOT_PROTOCOL,
|
||||
NODE_PROTOCOL,
|
||||
SPATIAL_PROTOCOL,
|
||||
FIELD_PROTOCOL,
|
||||
AUDIO_PROTOCOL,
|
||||
DRAWABLE_PROTOCOL,
|
||||
INPUT_PROTOCOL,
|
||||
ITEM_PROTOCOL,
|
||||
ITEM_CAMERA_PROTOCOL,
|
||||
ITEM_PANEL_PROTOCOL,
|
||||
];
|
||||
for aspect in protocols
|
||||
.into_iter()
|
||||
.filter_map(|p| Protocol::parse(p).ok())
|
||||
.flat_map(|v| v.aspects)
|
||||
{
|
||||
aspect_map.push((aspect.id, aspect.name));
|
||||
for m in aspect.members.into_iter() {
|
||||
match m._type {
|
||||
MemberType::Signal => &mut signal_map,
|
||||
MemberType::Method => &mut method_map,
|
||||
}
|
||||
.push((m.opcode, m.name));
|
||||
}
|
||||
}
|
||||
|
||||
let aspect_id_to_name = aspect_map
|
||||
.iter()
|
||||
.map(|(id, name)| {
|
||||
quote! {
|
||||
#id => #name,
|
||||
}
|
||||
})
|
||||
.reduce(fold_tokens);
|
||||
let aspect_id_to_name_fn = quote! {
|
||||
pub const fn aspect_id_to_name(id: u64) -> &'static str {
|
||||
match id {
|
||||
#aspect_id_to_name
|
||||
_ => "Unknown"
|
||||
}
|
||||
}
|
||||
};
|
||||
let method_id_to_name = method_map
|
||||
.iter()
|
||||
.map(|(id, name)| {
|
||||
quote! {
|
||||
#id => #name,
|
||||
}
|
||||
})
|
||||
.reduce(fold_tokens);
|
||||
let method_id_to_name_fn = quote! {
|
||||
pub const fn method_id_to_name(id: u64) -> &'static str {
|
||||
match id {
|
||||
#method_id_to_name
|
||||
_ => "Unknown"
|
||||
}
|
||||
}
|
||||
};
|
||||
let signal_id_to_name = signal_map
|
||||
.iter()
|
||||
.map(|(id, name)| {
|
||||
quote! {
|
||||
#id => #name,
|
||||
}
|
||||
})
|
||||
.reduce(fold_tokens);
|
||||
let signal_id_to_name_fn = quote! {
|
||||
pub const fn signal_id_to_name(id: u64) -> &'static str {
|
||||
match id {
|
||||
#signal_id_to_name
|
||||
_ => "Unknown"
|
||||
}
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
#aspect_id_to_name_fn
|
||||
#method_id_to_name_fn
|
||||
#signal_id_to_name_fn
|
||||
}.into()
|
||||
}
|
||||
|
||||
fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
|
||||
let protocol = Protocol::parse(protocol).unwrap();
|
||||
let interface = protocol
|
||||
@@ -64,11 +147,28 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
|
||||
};
|
||||
let aspect = generate_aspect(&Aspect {
|
||||
name: "interface".to_string(),
|
||||
id: 0,
|
||||
description: protocol.description.clone(),
|
||||
inherits: vec![],
|
||||
members: p.members,
|
||||
});
|
||||
quote!(#node_id #aspect)
|
||||
quote! {
|
||||
#node_id
|
||||
#aspect
|
||||
pub struct Interface;
|
||||
impl crate::nodes::AspectIdentifier for Interface {
|
||||
impl_aspect_for_interface_aspect_id!{}
|
||||
}
|
||||
impl crate::nodes::Aspect for Interface {
|
||||
impl_aspect_for_interface_aspect!{}
|
||||
}
|
||||
pub fn create_interface(client: &std::sync::Arc<crate::core::client::Client>) -> crate::core::error::Result<()>{
|
||||
let node = crate::nodes::Node::from_id(client,INTERFACE_NODE_ID,false);
|
||||
node.add_aspect(Interface);
|
||||
node.add_to_scenegraph()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let custom_enums = protocol
|
||||
@@ -177,11 +277,12 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
|
||||
Span::call_site(),
|
||||
);
|
||||
let client_side_members = client_members
|
||||
.map(generate_member)
|
||||
.map(|m| generate_member(aspect.id, m))
|
||||
.reduce(fold_tokens)
|
||||
.map(|t| {
|
||||
// TODO: properly import all dependencies
|
||||
quote! {
|
||||
#[allow(clippy::all)]
|
||||
pub mod #client_mod_name {
|
||||
use super::*;
|
||||
#t
|
||||
@@ -190,11 +291,6 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let aspect_trait_name = Ident::new(
|
||||
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
let opcodes = aspect
|
||||
.members
|
||||
.iter()
|
||||
@@ -219,31 +315,95 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
|
||||
let alias_info = generate_alias_info(aspect);
|
||||
|
||||
let server_side_members = server_members
|
||||
.map(generate_member)
|
||||
.map(|m| generate_member(aspect.id, m))
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
let add_node_members = aspect
|
||||
let aspect_trait_name = Ident::new(
|
||||
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
|
||||
Span::call_site(),
|
||||
);
|
||||
let run_signals = aspect
|
||||
.members
|
||||
.iter()
|
||||
.filter(|m| m.side == Side::Server)
|
||||
.map(generate_handler)
|
||||
.filter(|m| m._type == MemberType::Signal)
|
||||
.map(|m| generate_run_member(&aspect_trait_name, MemberType::Signal, m))
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
let run_methods = aspect
|
||||
.members
|
||||
.iter()
|
||||
.filter(|m| m.side == Side::Server)
|
||||
.filter(|m| m._type == MemberType::Method)
|
||||
.map(|m| generate_run_member(&aspect_trait_name, MemberType::Method, m))
|
||||
.reduce(fold_tokens)
|
||||
.map(|members| {
|
||||
quote! {
|
||||
fn add_node_members(node: &crate::nodes::Node) {
|
||||
#members
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let server_side_members = quote! {
|
||||
#[allow(clippy::all)]
|
||||
#[doc = #description]
|
||||
pub trait #aspect_trait_name {
|
||||
#add_node_members
|
||||
#server_side_members
|
||||
}
|
||||
};
|
||||
quote!(#opcodes #alias_info #client_side_members #server_side_members)
|
||||
let aspect_id_macro_name = Ident::new(
|
||||
&format!(
|
||||
"impl_aspect_for_{}_aspect_id",
|
||||
aspect.name.to_case(Case::Snake)
|
||||
),
|
||||
Span::call_site(),
|
||||
);
|
||||
let aspect_macro_name = Ident::new(
|
||||
&format!(
|
||||
"impl_aspect_for_{}_aspect",
|
||||
aspect.name.to_case(Case::Snake)
|
||||
),
|
||||
Span::call_site(),
|
||||
);
|
||||
let aspect_id = aspect.id;
|
||||
let aspect_macro = quote! {
|
||||
macro_rules! #aspect_id_macro_name {
|
||||
() => {
|
||||
const ID: u64 = #aspect_id;
|
||||
}
|
||||
}
|
||||
macro_rules! #aspect_macro_name {
|
||||
() => {
|
||||
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
||||
self
|
||||
}
|
||||
#[allow(clippy::all)]
|
||||
fn run_signal(
|
||||
&self,
|
||||
_calling_client: std::sync::Arc<crate::core::client::Client>,
|
||||
_node: std::sync::Arc<crate::nodes::Node>,
|
||||
_signal: u64,
|
||||
_message: crate::nodes::Message
|
||||
) -> Result<(), stardust_xr::scenegraph::ScenegraphError> {
|
||||
match _signal {
|
||||
#run_signals
|
||||
_ => Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound)
|
||||
}
|
||||
}
|
||||
#[allow(clippy::all)]
|
||||
fn run_method(
|
||||
&self,
|
||||
_calling_client: std::sync::Arc<crate::core::client::Client>,
|
||||
_node: std::sync::Arc<crate::nodes::Node>,
|
||||
_method: u64,
|
||||
_message: crate::nodes::Message,
|
||||
_method_response: crate::nodes::MethodResponseSender,
|
||||
) {
|
||||
match _method {
|
||||
#run_methods
|
||||
_ => {
|
||||
let _ = _method_response.send(Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
quote!(#opcodes #alias_info #client_side_members #server_side_members #aspect_macro)
|
||||
}
|
||||
|
||||
fn generate_alias_opcodes(aspect: &Aspect, side: Side, _type: MemberType) -> TokenStream {
|
||||
@@ -283,6 +443,7 @@ fn generate_alias_info(aspect: &Aspect) -> TokenStream {
|
||||
|
||||
quote! {
|
||||
lazy_static::lazy_static! {
|
||||
#[allow(clippy::all)]
|
||||
pub static ref #aspect_alias_info_name: crate::nodes::alias::AliasInfo = crate::nodes::alias::AliasInfo {
|
||||
server_signals: vec![#local_signals],
|
||||
server_methods: vec![#local_methods],
|
||||
@@ -293,8 +454,8 @@ fn generate_alias_info(aspect: &Aspect) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_member(member: &Member) -> TokenStream {
|
||||
let id = member.opcode;
|
||||
fn generate_member(aspect_id: u64, member: &Member) -> TokenStream {
|
||||
let opcode = member.opcode;
|
||||
let name = Ident::new(&member.name.to_case(Case::Snake), Span::call_site());
|
||||
let description = &member.description;
|
||||
|
||||
@@ -326,38 +487,38 @@ fn generate_member(member: &Member) -> TokenStream {
|
||||
.unwrap_or_else(|| quote!(()));
|
||||
|
||||
match (side, _type) {
|
||||
(Side::Client, MemberType::Method) => {
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
pub async fn #name(#argument_decls) -> color_eyre::eyre::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
|
||||
_node.execute_remote_method_typed(#id, &(#argument_uses), vec![]).await
|
||||
}
|
||||
}
|
||||
}
|
||||
(Side::Client, MemberType::Signal) => {
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
pub fn #name(#argument_decls) -> color_eyre::eyre::Result<()> {
|
||||
pub fn #name(#argument_decls) -> crate::core::error::Result<()> {
|
||||
let serialized = stardust_xr::schemas::flex::serialize((#argument_uses))?;
|
||||
_node.send_remote_signal(#id, serialized)
|
||||
_node.send_remote_signal(#aspect_id, #opcode, serialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
(Side::Server, MemberType::Method) => {
|
||||
(Side::Client, MemberType::Method) => {
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
fn #name(#argument_decls) -> impl std::future::Future<Output = color_eyre::eyre::Result<#return_type>> + Send + 'static;
|
||||
pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
|
||||
_node.execute_remote_method_typed(#aspect_id, #opcode, &(#argument_uses), vec![]).await
|
||||
}
|
||||
}
|
||||
}
|
||||
(Side::Server, MemberType::Signal) => {
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
fn #name(#argument_decls) -> color_eyre::eyre::Result<()>;
|
||||
fn #name(#argument_decls) -> crate::core::error::Result<()>;
|
||||
}
|
||||
}
|
||||
(Side::Server, MemberType::Method) => {
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
fn #name(#argument_decls) -> impl std::future::Future<Output = crate::core::error::Result<#return_type>> + Send + Sync + 'static;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn generate_handler(member: &Member) -> TokenStream {
|
||||
fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member) -> TokenStream {
|
||||
let opcode = member.opcode;
|
||||
let member_name_ident = Ident::new(&member.name, Span::call_site());
|
||||
|
||||
@@ -379,7 +540,10 @@ fn generate_handler(member: &Member) -> TokenStream {
|
||||
.clone()
|
||||
.zip(argument_types)
|
||||
.map(|(argument_names, argument_types)| {
|
||||
quote!(let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;)
|
||||
quote!{
|
||||
#[allow(unused_parens)]
|
||||
let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let serialize = generate_argument_serialize(
|
||||
@@ -393,21 +557,19 @@ fn generate_handler(member: &Member) -> TokenStream {
|
||||
.map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
|
||||
.reduce(|a, b| quote!(#a, #b))
|
||||
.unwrap_or_default();
|
||||
match member._type {
|
||||
match _type {
|
||||
MemberType::Signal => quote! {
|
||||
node.add_local_signal(#opcode, |_node, _calling_client, _message| {
|
||||
#opcode => (move || {
|
||||
#deserialize
|
||||
Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
|
||||
});
|
||||
<Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
|
||||
})().map_err(|e: crate::core::error::ServerError| stardust_xr::scenegraph::ScenegraphError::MemberError { error: e.to_string() }),
|
||||
},
|
||||
MemberType::Method => quote! {
|
||||
node.add_local_method(#opcode, |_node, _calling_client, _message, _method_response| {
|
||||
_method_response.wrap_async(async move {
|
||||
#deserialize
|
||||
let result = Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?;
|
||||
Ok((#serialize, Vec::new()))
|
||||
});
|
||||
});
|
||||
#opcode => _method_response.wrap_async(async move {
|
||||
#deserialize
|
||||
let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?;
|
||||
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -441,18 +603,18 @@ fn generate_argument_deserialize(
|
||||
}
|
||||
if optional {
|
||||
let mapping = generate_argument_deserialize("o", argument_type, false);
|
||||
return quote!(#name.map(|o| Ok::<_, color_eyre::eyre::Report>(#mapping)).transpose()?);
|
||||
return quote!(#name.map(|o| Ok::<_, crate::core::error::ServerError>(#mapping)).transpose()?);
|
||||
}
|
||||
|
||||
match argument_type {
|
||||
ArgumentType::Color => quote!(color::rgba_linear!(#name[0], #name[1], #name[2], #name[3])),
|
||||
ArgumentType::Vec(v) => {
|
||||
let mapping = generate_argument_deserialize("a", v, false);
|
||||
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
|
||||
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<crate::core::error::Result<Vec<_>>>()?)
|
||||
}
|
||||
ArgumentType::Map(v) => {
|
||||
let mapping = generate_argument_deserialize("a", v, false);
|
||||
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<crate::core::error::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||
}
|
||||
_ => quote!(#name),
|
||||
}
|
||||
@@ -474,11 +636,11 @@ fn generate_argument_serialize(
|
||||
ArgumentType::Color => quote!([#name.c.r, #name.c.g, #name.c.b, #name.a]),
|
||||
ArgumentType::Vec(v) => {
|
||||
let mapping = generate_argument_serialize("a", v, false);
|
||||
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
|
||||
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<crate::core::error::Result<Vec<_>>>()?)
|
||||
}
|
||||
ArgumentType::Map(v) => {
|
||||
let mapping = generate_argument_serialize("a", v, false);
|
||||
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<crate::core::error::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||
}
|
||||
_ => quote!(#name),
|
||||
}
|
||||
@@ -511,6 +673,7 @@ fn argument_type_option_name(argument_type: &ArgumentType) -> String {
|
||||
ArgumentType::Union(u) => u.clone(),
|
||||
ArgumentType::Struct(s) => s.clone(),
|
||||
ArgumentType::Node { _type, .. } => _type.clone(),
|
||||
ArgumentType::Fd => "File Descriptor".to_string(),
|
||||
}
|
||||
}
|
||||
fn generate_argument_type(
|
||||
@@ -607,6 +770,9 @@ fn generate_argument_type(
|
||||
quote!(std::sync::Arc<crate::nodes::Node>)
|
||||
}
|
||||
}
|
||||
ArgumentType::Fd => {
|
||||
quote!(&std::os::fd::OwnedFd)
|
||||
}
|
||||
};
|
||||
|
||||
if optional {
|
||||
|
||||
12
justfile
Normal file
12
justfile
Normal file
@@ -0,0 +1,12 @@
|
||||
PREFIX := "usr"
|
||||
BINARY := PREFIX / "bin"
|
||||
DESTDIR := "/"
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
test:
|
||||
cargo test
|
||||
|
||||
install:
|
||||
install -Dm755 target/release/stardust-xr-server {{ DESTDIR }}{{ BINARY }}/stardust-xr-server
|
||||
195
src/bevy_plugin.rs
Normal file
195
src/bevy_plugin.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use bevy::{
|
||||
app::MainScheduleOrder,
|
||||
ecs::schedule::{ExecutorKind, ScheduleLabel},
|
||||
math::bounding::Aabb3d,
|
||||
pbr::{DefaultOpaqueRendererMethod, GpuMeshPreprocessPlugin, MeshRenderPlugin},
|
||||
prelude::*,
|
||||
render::extract_resource::ExtractResourcePlugin,
|
||||
};
|
||||
use bevy_mod_openxr::session::OxrSession;
|
||||
use bevy_mod_xr::session::{session_available, XrFirst, XrSessionCreated};
|
||||
use once_cell::sync::OnceCell;
|
||||
use openxr::ReferenceSpaceType;
|
||||
use stardust_xr::values::color::{color_space::LinearRgb, AlphaColor, Rgb};
|
||||
|
||||
use crate::DefaultMaterial;
|
||||
|
||||
pub struct StardustBevyPlugin;
|
||||
|
||||
pub static DESTROY_ENTITY: DestroySender = DestroySender(OnceCell::new());
|
||||
|
||||
pub struct DestroySender(OnceCell<crossbeam_channel::Sender<Entity>>);
|
||||
impl Deref for DestroySender {
|
||||
type Target = crossbeam_channel::Sender<Entity>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.get().unwrap()
|
||||
}
|
||||
}
|
||||
#[derive(Resource, Deref)]
|
||||
struct DestroyEntityReader(crossbeam_channel::Receiver<Entity>);
|
||||
|
||||
#[derive(Resource, Deref)]
|
||||
pub struct DbusConnection(pub zbus::Connection);
|
||||
|
||||
#[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct InputUpdate;
|
||||
#[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct StardustFirst;
|
||||
impl Plugin for StardustBevyPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
DESTROY_ENTITY
|
||||
.0
|
||||
.set(tx)
|
||||
.expect("unable to set destroy entity sender, yell at schmarni pls thx");
|
||||
app.insert_resource(DestroyEntityReader(rx));
|
||||
app.init_schedule(StardustExtract);
|
||||
let labels = &mut app.world_mut().resource_mut::<MainScheduleOrder>().labels;
|
||||
info!("test: {labels:?}");
|
||||
labels.insert(labels.len() - 2, StardustExtract.intern());
|
||||
app.add_systems(Startup, spawn_camera.run_if(not(session_available)));
|
||||
app.add_systems(XrSessionCreated, make_view_space);
|
||||
let mut schedule = Schedule::new(InputUpdate);
|
||||
schedule.set_executor_kind(ExecutorKind::Simple);
|
||||
app.add_schedule(schedule);
|
||||
|
||||
let mut schedule = Schedule::new(StardustFirst);
|
||||
schedule.set_executor_kind(ExecutorKind::Simple);
|
||||
app.add_schedule(schedule);
|
||||
|
||||
let labels = &mut app.world_mut().resource_mut::<MainScheduleOrder>().labels;
|
||||
let xr_first_intern = (XrFirst).intern();
|
||||
if labels.remove(0) != xr_first_intern {
|
||||
panic!("first schedule was not XrFirst!");
|
||||
}
|
||||
labels.insert(0, (StardustFirst).intern());
|
||||
app.add_systems(First, yeet_entities);
|
||||
// app.add_observer(
|
||||
// |trigger: Trigger<OnInsert, MeshMaterial3d<DefaultMaterial>>,
|
||||
// query: Query<&MeshMaterial3d<DefaultMaterial>>,
|
||||
// mut mats: ResMut<Assets<DefaultMaterial>>| {
|
||||
// let Ok(handle) = query.get(trigger.entity()) else {
|
||||
// return;
|
||||
// };
|
||||
// if let Some(mat) = mats.get_mut(handle) {
|
||||
// if matches!(mat.alpha_mode, AlphaMode::Blend | AlphaMode::Mask(_)) {
|
||||
// mat.alpha_mode = AlphaMode::AlphaToCoverage
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
fn yeet_entities(
|
||||
mut cmds: Commands,
|
||||
query: Query<Entity, With<TemporaryEntity>>,
|
||||
reader: Res<DestroyEntityReader>,
|
||||
) {
|
||||
query.iter().for_each(|e| {
|
||||
info!("yeeting component entities");
|
||||
cmds.entity(e).despawn_recursive();
|
||||
});
|
||||
reader
|
||||
.0
|
||||
.try_iter()
|
||||
.for_each(|e| cmds.entity(e).despawn_recursive());
|
||||
}
|
||||
|
||||
fn make_view_space(mut cmds: Commands, session: Res<OxrSession>) {
|
||||
// idk what errors this function returns
|
||||
let view_space = session
|
||||
.create_reference_space(ReferenceSpaceType::VIEW, Transform::IDENTITY)
|
||||
.unwrap();
|
||||
// this locates the view space against the default reference space (stage i belive) and sets
|
||||
// the transform relative to the XrTrackingRoot
|
||||
cmds.spawn((view_space.0, ViewLocation));
|
||||
}
|
||||
|
||||
fn spawn_camera(mut cmds: Commands) {
|
||||
cmds.spawn((Camera3d::default(), ViewLocation));
|
||||
}
|
||||
pub trait StardustAabb3dExt {
|
||||
fn grown_box(&self, aabb: &Self, opt_box_transform: Option<impl Into<Mat4>>) -> Self;
|
||||
fn grown_point(&self, pt: impl Into<Vec3>) -> Self;
|
||||
}
|
||||
impl StardustAabb3dExt for Aabb3d {
|
||||
fn grown_box(&self, other: &Self, opt_box_transform: Option<impl Into<Mat4>>) -> Self {
|
||||
let mat = opt_box_transform.map(|m| m.into());
|
||||
let other_min = mat
|
||||
.as_ref()
|
||||
.map(|v| v.transform_point3a(other.min))
|
||||
.unwrap_or(other.min);
|
||||
let other_max = mat
|
||||
.as_ref()
|
||||
.map(|v| v.transform_point3a(other.max))
|
||||
.unwrap_or(other.max);
|
||||
let tmp = self.grown_point(other_min);
|
||||
tmp.grown_point(other_max)
|
||||
}
|
||||
|
||||
fn grown_point(&self, pt: impl Into<Vec3>) -> Self {
|
||||
let pt = pt.into();
|
||||
let mut min = self.min;
|
||||
let mut max = self.max;
|
||||
if pt.x > max.x {
|
||||
max.x = pt.x;
|
||||
} else if pt.x < min.x {
|
||||
min.x = pt.x;
|
||||
}
|
||||
if pt.y > max.y {
|
||||
max.y = pt.y;
|
||||
} else if pt.y < min.y {
|
||||
min.y = pt.y;
|
||||
}
|
||||
if pt.z > max.z {
|
||||
max.z = pt.z;
|
||||
} else if pt.z < min.z {
|
||||
min.z = pt.z;
|
||||
}
|
||||
|
||||
Aabb3d { min, max }
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
pub struct TemporaryEntity;
|
||||
#[derive(Component, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[require(GlobalTransform)]
|
||||
pub struct ViewLocation;
|
||||
#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, Deref)]
|
||||
pub struct MainWorldEntity(pub Entity);
|
||||
|
||||
pub struct DummyPbrPlugin;
|
||||
|
||||
impl Plugin for DummyPbrPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let use_gpu_instance_buffer_builder = true;
|
||||
app.init_asset::<StandardMaterial>();
|
||||
app.add_plugins((
|
||||
GpuMeshPreprocessPlugin {
|
||||
use_gpu_instance_buffer_builder,
|
||||
},
|
||||
MeshRenderPlugin {
|
||||
use_gpu_instance_buffer_builder,
|
||||
},
|
||||
));
|
||||
app.register_type::<DefaultOpaqueRendererMethod>()
|
||||
.init_resource::<DefaultOpaqueRendererMethod>()
|
||||
.add_plugins(ExtractResourcePlugin::<DefaultOpaqueRendererMethod>::default());
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use super::{
|
||||
use crate::{
|
||||
core::{registry::OwnedRegistry, task},
|
||||
nodes::{
|
||||
audio, data, drawable, fields, input, items,
|
||||
audio, drawable, fields, input, items,
|
||||
root::{ClientState, Root},
|
||||
spatial, Node,
|
||||
},
|
||||
@@ -112,9 +112,8 @@ impl Client {
|
||||
fields::create_interface(&client)?;
|
||||
drawable::create_interface(&client)?;
|
||||
audio::create_interface(&client)?;
|
||||
data::create_interface(&client)?;
|
||||
input::create_interface(&client)?;
|
||||
items::camera::create_interface(&client)?;
|
||||
// items::camera::create_interface(&client)?;
|
||||
items::panel::create_interface(&client)?;
|
||||
|
||||
let _ = client.state.set(state.apply_to(&client));
|
||||
|
||||
74
src/core/error.rs
Normal file
74
src/core/error.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::any::TypeId;
|
||||
|
||||
use color_eyre::eyre::Report;
|
||||
use stardust_xr::{
|
||||
messenger::MessengerError,
|
||||
schemas::flex::{
|
||||
flexbuffers::{DeserializationError, ReaderError},
|
||||
FlexSerializeError,
|
||||
},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T, E = ServerError> = std::result::Result<T, E>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ServerError {
|
||||
#[error("Internal: Unable to get client")]
|
||||
NoClient,
|
||||
#[error("Messenger does not exist for this node")]
|
||||
NoMessenger,
|
||||
#[error("Could not find resource")]
|
||||
NoResource,
|
||||
#[error("Messenger error: {0}")]
|
||||
MessengerError(#[from] MessengerError),
|
||||
#[error("Remote method error: {0}")]
|
||||
RemoteMethodError(String),
|
||||
#[error("Serialization error: {0}")]
|
||||
SerializationError(#[from] FlexSerializeError),
|
||||
#[error("Deserialization error: {0}")]
|
||||
DeserializationError(#[from] DeserializationError),
|
||||
#[error("Reader error: {0}")]
|
||||
ReaderError(#[from] ReaderError),
|
||||
#[error("Aspect {} does not exist for node", 0.to_string())]
|
||||
NoAspect(TypeId),
|
||||
#[error("{0}")]
|
||||
Report(#[from] Report),
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($msg:literal $(,)?) => {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($msg)));
|
||||
};
|
||||
($err:expr $(,)?) => {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($err)));
|
||||
};
|
||||
($fmt:expr, $($arg:tt)*) => {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($fmt, $($arg)*)));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure {
|
||||
($cond:expr $(,)?) => {
|
||||
if !$cond {
|
||||
$crate::ensure!($cond, concat!("Condition failed: `", stringify!($cond), "`"))
|
||||
}
|
||||
};
|
||||
($cond:expr, $msg:literal $(,)?) => {
|
||||
if !$cond {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($msg)));
|
||||
}
|
||||
};
|
||||
($cond:expr, $err:expr $(,)?) => {
|
||||
if !$cond {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($err)));
|
||||
}
|
||||
};
|
||||
($cond:expr, $fmt:expr, $($arg:tt)*) => {
|
||||
if !$cond {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($fmt, $($arg)*)));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#[macro_export]
|
||||
macro_rules! create_interface {
|
||||
($iface:ident) => {
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::from_id(client, INTERFACE_NODE_ID, false);
|
||||
<$iface as self::InterfaceAspect>::add_node_members(&node);
|
||||
node.add_to_scenegraph()?;
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,8 +2,9 @@ pub mod client;
|
||||
pub mod client_state;
|
||||
pub mod delta;
|
||||
pub mod destroy_queue;
|
||||
pub mod idl_utils;
|
||||
pub mod error;
|
||||
pub mod registry;
|
||||
pub mod resource;
|
||||
pub mod scenegraph;
|
||||
pub mod task;
|
||||
pub mod queued_mutex;
|
||||
|
||||
119
src/core/queued_mutex.rs
Normal file
119
src/core/queued_mutex.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
sync::atomic::Ordering,
|
||||
};
|
||||
|
||||
use parking_lot::{
|
||||
lock_api::{MutexGuard, RwLockReadGuard},
|
||||
Mutex, RawMutex, RawRwLock, RwLock,
|
||||
};
|
||||
use portable_atomic::AtomicU8;
|
||||
|
||||
pub struct QueuedMutex<T: Clone> {
|
||||
mutable: Mutex<Option<T>>,
|
||||
readers: AtomicU8,
|
||||
inner: RwLock<T>,
|
||||
}
|
||||
|
||||
impl<T: Clone + Default> Default for QueuedMutex<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> QueuedMutex<T> {
|
||||
pub const fn new(value: T) -> QueuedMutex<T> {
|
||||
Self {
|
||||
mutable: Mutex::new(None),
|
||||
readers: AtomicU8::new(0),
|
||||
inner: RwLock::new(value),
|
||||
}
|
||||
}
|
||||
pub fn lock(&self) -> QueuedMutexLockGuard<'_, T> {
|
||||
let mut guard = self.mutable.lock();
|
||||
if guard.is_none() {
|
||||
guard.replace(self.inner.read().clone());
|
||||
}
|
||||
QueuedMutexLockGuard { mutex: self, guard }
|
||||
}
|
||||
pub fn read_now(&self) -> QueuedMutexReadGuard<'_, T> {
|
||||
let guard = self.inner.read();
|
||||
self.readers.add(1, Ordering::Relaxed);
|
||||
QueuedMutexReadGuard {
|
||||
mutex: self,
|
||||
guard: Some(guard),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueuedMutexLockGuard<'a, T: Clone> {
|
||||
mutex: &'a QueuedMutex<T>,
|
||||
guard: MutexGuard<'a, RawMutex, Option<T>>,
|
||||
}
|
||||
impl<T: Clone> Deref for QueuedMutexLockGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self.guard.as_ref() {
|
||||
Some(v) => v,
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Clone> DerefMut for QueuedMutexLockGuard<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
match self.guard.as_mut() {
|
||||
Some(v) => v,
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Drop for QueuedMutexLockGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
if self.mutex.readers.load(Ordering::Relaxed) != 0 {
|
||||
return;
|
||||
}
|
||||
let mut write_lock = self.mutex.inner.write();
|
||||
*write_lock = match self.guard.take() {
|
||||
Some(v) => v,
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueuedMutexReadGuard<'a, T: Clone> {
|
||||
mutex: &'a QueuedMutex<T>,
|
||||
guard: Option<RwLockReadGuard<'a, RawRwLock, T>>,
|
||||
}
|
||||
|
||||
impl<T: Clone> Deref for QueuedMutexReadGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self.guard.as_ref() {
|
||||
Some(v) => v,
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Drop for QueuedMutexReadGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
drop(self.guard.take());
|
||||
if self
|
||||
.mutex
|
||||
.readers
|
||||
.fetch_sub(1, Ordering::Relaxed)
|
||||
.wrapping_sub(1)
|
||||
!= 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut write_lock = self.mutex.inner.write();
|
||||
if let Some(v) = self.mutex.mutable.lock().take() {
|
||||
*write_lock = v
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::alias::get_original;
|
||||
use crate::nodes::Node;
|
||||
use crate::TOKIO;
|
||||
use crate::{core::client::Client, nodes::Message};
|
||||
use color_eyre::eyre::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -15,6 +16,8 @@ use std::sync::{Arc, Weak};
|
||||
use tokio::sync::oneshot;
|
||||
use tracing::{debug, debug_span};
|
||||
|
||||
stardust_xr_server_codegen::codegen_id_to_name_functions!();
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Scenegraph {
|
||||
pub(super) client: OnceCell<Weak<Client>>,
|
||||
@@ -59,26 +62,26 @@ impl MethodResponseSender {
|
||||
// ) {
|
||||
// let _ = self.0.send(map_method_return(result));
|
||||
// }
|
||||
pub fn wrap_sync<F: FnOnce() -> color_eyre::eyre::Result<Message>>(self, f: F) {
|
||||
self.send(f().map_err(|e| ScenegraphError::MethodError {
|
||||
pub fn wrap_sync<F: FnOnce() -> crate::core::error::Result<Message>>(self, f: F) {
|
||||
self.send(f().map_err(|e| ScenegraphError::MemberError {
|
||||
error: e.to_string(),
|
||||
}))
|
||||
}
|
||||
pub fn wrap_async<T: Serialize>(
|
||||
self,
|
||||
f: impl Future<Output = color_eyre::eyre::Result<(T, Vec<OwnedFd>)>> + Send + 'static,
|
||||
f: impl Future<Output = Result<(T, Vec<OwnedFd>)>> + Send + 'static,
|
||||
) {
|
||||
tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) });
|
||||
TOKIO.spawn(async move { self.0.send(map_method_return(f.await)) });
|
||||
}
|
||||
}
|
||||
fn map_method_return<T: Serialize>(
|
||||
result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
|
||||
result: Result<(T, Vec<OwnedFd>)>,
|
||||
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
|
||||
let (value, fds) = result.map_err(|e| ScenegraphError::MethodError {
|
||||
let (value, fds) = result.map_err(|e| ScenegraphError::MemberError {
|
||||
error: e.to_string(),
|
||||
})?;
|
||||
|
||||
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MethodError {
|
||||
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MemberError {
|
||||
error: format!("Internal: Serialization failed: {e}"),
|
||||
})?;
|
||||
Ok((serialized_value, fds))
|
||||
@@ -86,19 +89,29 @@ fn map_method_return<T: Serialize>(
|
||||
impl scenegraph::Scenegraph for Scenegraph {
|
||||
fn send_signal(
|
||||
&self,
|
||||
node: u64,
|
||||
node_id: u64,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
data: &[u8],
|
||||
fds: Vec<OwnedFd>,
|
||||
) -> Result<(), ScenegraphError> {
|
||||
let Some(client) = self.get_client() else {
|
||||
return Err(ScenegraphError::SignalNotFound);
|
||||
return Err(ScenegraphError::NodeNotFound);
|
||||
};
|
||||
debug_span!("Handle signal", node, method).in_scope(|| {
|
||||
self.get_node(node)
|
||||
debug_span!(
|
||||
"Handle signal",
|
||||
aspect_id,
|
||||
aspect_name = aspect_id_to_name(aspect_id),
|
||||
node_id,
|
||||
method,
|
||||
signal_name = signal_id_to_name(method)
|
||||
)
|
||||
.in_scope(|| {
|
||||
self.get_node(node_id)
|
||||
.ok_or(ScenegraphError::NodeNotFound)?
|
||||
.send_local_signal(
|
||||
client,
|
||||
aspect_id,
|
||||
method,
|
||||
Message {
|
||||
data: data.to_vec(),
|
||||
@@ -109,23 +122,32 @@ impl scenegraph::Scenegraph for Scenegraph {
|
||||
}
|
||||
fn execute_method(
|
||||
&self,
|
||||
node: u64,
|
||||
node_id: u64,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
data: &[u8],
|
||||
fds: Vec<OwnedFd>,
|
||||
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
|
||||
) {
|
||||
let Some(client) = self.get_client() else {
|
||||
let _ = response.send(Err(ScenegraphError::MethodNotFound));
|
||||
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
||||
return;
|
||||
};
|
||||
debug!(node, method, "Handle method");
|
||||
let Some(node) = self.get_node(node) else {
|
||||
debug!(
|
||||
aspect_id,
|
||||
aspect_name = aspect_id_to_name(aspect_id),
|
||||
node_id,
|
||||
method,
|
||||
method_name = method_id_to_name(method),
|
||||
"Handle method"
|
||||
);
|
||||
let Some(node) = self.get_node(node_id) else {
|
||||
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
||||
return;
|
||||
};
|
||||
node.execute_local_method(
|
||||
client,
|
||||
aspect_id,
|
||||
method,
|
||||
Message {
|
||||
data: data.to_vec(),
|
||||
|
||||
@@ -2,6 +2,8 @@ use color_eyre::eyre::Result;
|
||||
use std::future::Future;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::TOKIO;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub fn new<
|
||||
F: FnOnce() -> S,
|
||||
@@ -13,7 +15,7 @@ pub fn new<
|
||||
async_future: A,
|
||||
) -> Result<JoinHandle<O>> {
|
||||
#[cfg(not(feature = "profile_tokio"))]
|
||||
let result = Ok(tokio::task::spawn(async_future));
|
||||
let result = Ok(TOKIO.spawn(async_future));
|
||||
#[cfg(feature = "profile_tokio")]
|
||||
let result = tokio::task::Builder::new()
|
||||
.name(name_fn().as_ref())
|
||||
|
||||
476
src/main.rs
476
src/main.rs
@@ -1,41 +1,87 @@
|
||||
#![allow(clippy::empty_docs)]
|
||||
pub mod bevy_plugin;
|
||||
mod core;
|
||||
mod nodes;
|
||||
mod objects;
|
||||
pub mod oxr_render_plugin;
|
||||
mod session;
|
||||
#[cfg(feature = "wayland")]
|
||||
mod wayland;
|
||||
|
||||
use crate::core::destroy_queue;
|
||||
use crate::nodes::items::camera;
|
||||
use crate::nodes::{audio, drawable, input};
|
||||
// use crate::nodes::items::camera;
|
||||
use crate::nodes::input;
|
||||
|
||||
use bevy::a11y::AccessibilityPlugin;
|
||||
use bevy::app::{
|
||||
App, AppExit, PluginGroup, PostUpdate, ScheduleRunnerPlugin, Startup,
|
||||
TerminalCtrlCHandlerPlugin, Update,
|
||||
};
|
||||
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::pbr::PbrPlugin;
|
||||
use bevy::prelude::{
|
||||
on_event, Camera3d, ClearColor, Commands, Entity, EventReader, HierarchyPlugin, ImagePlugin,
|
||||
IntoSystemConfigs, Local, Query, Res, ResMut, Resource, TransformPlugin, With, World,
|
||||
};
|
||||
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
|
||||
use bevy::render::RenderPlugin;
|
||||
use bevy::scene::ScenePlugin;
|
||||
use bevy::text::{Font, FontLoader};
|
||||
use bevy::time::Time;
|
||||
use bevy::window::WindowPlugin;
|
||||
use bevy::winit::{WakeUp, WinitPlugin};
|
||||
use bevy::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};
|
||||
use bevy_mod_openxr::init::{should_run_frame_loop, OxrInitPlugin};
|
||||
use bevy_mod_openxr::render::{update_cameras, OxrRenderPlugin};
|
||||
use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrGraphicsInfo};
|
||||
use bevy_mod_openxr::session::OxrSession;
|
||||
use bevy_mod_openxr::spaces::OxrSpaceExt;
|
||||
use bevy_mod_openxr::types::{AppInfo, Version};
|
||||
use bevy_mod_openxr::{add_xr_plugins, openxr_session_running};
|
||||
use bevy_mod_xr::session::{XrFirst, XrPreDestroySession, XrSessionCreated};
|
||||
use bevy_mod_xr::spaces::XrPrimaryReferenceSpace;
|
||||
use bevy_plugin::{DbusConnection, DummyPbrPlugin, InputUpdate, StardustBevyPlugin, StardustFirst};
|
||||
use bevy_sk::skytext::SphericalHarmonicsPlugin;
|
||||
use clap::Parser;
|
||||
use core::client::Client;
|
||||
use core::task;
|
||||
use directories::ProjectDirs;
|
||||
use nodes::audio::StardustSoundPlugin;
|
||||
use nodes::drawable::lines::BevyLinesPlugin;
|
||||
use nodes::drawable::model::StardustModelPlugin;
|
||||
use nodes::drawable::text::StardustTextPlugin;
|
||||
use objects::input::sk_controller::StardustControllerPlugin;
|
||||
use objects::input::sk_hand::StardustHandPlugin;
|
||||
use objects::ServerObjects;
|
||||
use once_cell::sync::OnceCell;
|
||||
use openxr::OverlaySessionCreateFlagsEXTX;
|
||||
use oxr_render_plugin::StardustOxrRenderPlugin;
|
||||
use session::{launch_start, save_session};
|
||||
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
|
||||
use stardust_xr::server;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use stereokit_rust::material::Material;
|
||||
use stereokit_rust::shader::Shader;
|
||||
use stereokit_rust::sk::{sk_quit, AppMode, DepthMode, OriginMode, QuitReason, SkSettings};
|
||||
use stereokit_rust::system::{LogLevel, Renderer};
|
||||
use stereokit_rust::tex::{SHCubemap, Tex, TexFormat, TexType};
|
||||
use stereokit_rust::ui::Ui;
|
||||
use stereokit_rust::util::{Color128, Time};
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::sync::Notify;
|
||||
use tracing::metadata::LevelFilter;
|
||||
use tracing::{debug_span, error, info};
|
||||
use tracing::{debug_span, error, info, warn};
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
use zbus::fdo::ObjectManager;
|
||||
use zbus::Connection;
|
||||
|
||||
pub type DefaultMaterial = bevy_sk::vr_materials::PbrMaterial;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct CliArgs {
|
||||
@@ -43,6 +89,10 @@ struct CliArgs {
|
||||
#[clap(short, long, action)]
|
||||
flatscreen: bool,
|
||||
|
||||
/// Force Pipelined Rending, improving fps at the cost of latency
|
||||
#[clap(short, long, action)]
|
||||
pipelined_rendering: bool,
|
||||
|
||||
/// If monado insists on emulating them, set this flag...we want the raw input
|
||||
#[clap(long)]
|
||||
disable_controllers: bool,
|
||||
@@ -68,11 +118,26 @@ struct CliArgs {
|
||||
}
|
||||
|
||||
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
|
||||
static TOKIO: RuntimeWrapper = RuntimeWrapper(OnceCell::new());
|
||||
|
||||
// #[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
color_eyre::install().unwrap();
|
||||
struct RuntimeWrapper(OnceCell<tokio::runtime::Runtime>);
|
||||
impl Deref for RuntimeWrapper {
|
||||
type Target = tokio::runtime::Runtime;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.get().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> color_eyre::Result<AppExit> {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
TOKIO.0.set(runtime).unwrap();
|
||||
TOKIO.block_on(setup())
|
||||
}
|
||||
async fn setup() -> color_eyre::Result<AppExit> {
|
||||
color_eyre::install()?;
|
||||
|
||||
let registry = tracing_subscriber::registry();
|
||||
|
||||
@@ -137,13 +202,26 @@ async fn main() {
|
||||
.await
|
||||
.expect("Couldn't add the object manager");
|
||||
|
||||
let object_registry = ObjectRegistry::new(&dbus_connection).await.expect(
|
||||
"Couldn't make the object registry to find all objects with given interfaces in d-bus",
|
||||
);
|
||||
|
||||
let sk_ready_notifier = Arc::new(Notify::new());
|
||||
let stereokit_loop = tokio::task::spawn_blocking({
|
||||
let sk_ready_notifier = sk_ready_notifier.clone();
|
||||
let project_dirs = project_dirs.clone();
|
||||
let cli_args = cli_args.clone();
|
||||
let dbus_connection = dbus_connection.clone();
|
||||
move || stereokit_loop(sk_ready_notifier, project_dirs, cli_args, dbus_connection)
|
||||
move || {
|
||||
bevy_loop(
|
||||
sk_ready_notifier,
|
||||
project_dirs,
|
||||
cli_args,
|
||||
dbus_connection,
|
||||
object_registry,
|
||||
false,
|
||||
)
|
||||
}
|
||||
});
|
||||
sk_ready_notifier.notified().await;
|
||||
let mut startup_children = project_dirs
|
||||
@@ -151,10 +229,7 @@ async fn main() {
|
||||
.map(|project_dirs| launch_start(&cli_args, project_dirs))
|
||||
.unwrap_or_default();
|
||||
|
||||
tokio::select! {
|
||||
_ = stereokit_loop => (),
|
||||
_ = tokio::signal::ctrl_c() => unsafe {sk_quit(QuitReason::SystemClose)},
|
||||
}
|
||||
let exit = stereokit_loop.await?;
|
||||
info!("Stopping...");
|
||||
if let Some(project_dirs) = project_dirs {
|
||||
save_session(&project_dirs).await;
|
||||
@@ -164,63 +239,210 @@ async fn main() {
|
||||
}
|
||||
|
||||
info!("Cleanly shut down Stardust");
|
||||
Ok(exit)
|
||||
}
|
||||
|
||||
fn stereokit_loop(
|
||||
fn bevy_loop(
|
||||
sk_ready_notifier: Arc<Notify>,
|
||||
project_dirs: Option<ProjectDirs>,
|
||||
args: CliArgs,
|
||||
dbus_connection: Connection,
|
||||
) {
|
||||
let sk = SkSettings::default()
|
||||
.app_name("Stardust XR")
|
||||
.mode(if args.flatscreen {
|
||||
AppMode::Simulator
|
||||
} else {
|
||||
AppMode::XR
|
||||
})
|
||||
.depth_mode(DepthMode::D32)
|
||||
.log_filter(match EnvFilter::from_default_env().max_level_hint() {
|
||||
Some(LevelFilter::ERROR) => LogLevel::Error,
|
||||
Some(LevelFilter::WARN) => LogLevel::Warning,
|
||||
Some(LevelFilter::INFO) => LogLevel::Inform,
|
||||
Some(LevelFilter::DEBUG) => LogLevel::Diagnostic,
|
||||
Some(LevelFilter::TRACE) => LogLevel::Diagnostic,
|
||||
Some(LevelFilter::OFF) => LogLevel::None,
|
||||
None => LogLevel::Warning,
|
||||
})
|
||||
.overlay_app(args.overlay_priority.is_some())
|
||||
.overlay_priority(args.overlay_priority.unwrap_or(u32::MAX))
|
||||
.disable_desktop_input_window(true)
|
||||
.origin(OriginMode::Local)
|
||||
.init()
|
||||
.expect("StereoKit failed to initialize");
|
||||
info!("Init StereoKit");
|
||||
|
||||
Renderer::multisample(0);
|
||||
Material::default().shader(Shader::pbr_clip());
|
||||
Ui::enable_far_interact(false);
|
||||
|
||||
// Skytex/light stuff
|
||||
{
|
||||
if let Some(sky) = project_dirs
|
||||
.as_ref()
|
||||
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
|
||||
.filter(|f| f.exists())
|
||||
.and_then(|p| SHCubemap::from_cubemap_equirectangular(p, true, 100).ok())
|
||||
{
|
||||
sky.render_as_sky();
|
||||
} else {
|
||||
Renderer::skytex(Tex::gen_color(
|
||||
Color128::BLACK,
|
||||
1,
|
||||
1,
|
||||
TexType::Cubemap,
|
||||
TexFormat::RGBA32,
|
||||
));
|
||||
object_registry: ObjectRegistry,
|
||||
headless: bool,
|
||||
) -> AppExit {
|
||||
let mut bevy_app = App::new();
|
||||
// let base = (DefaultPlugins)
|
||||
// .build()
|
||||
// .disable::<PipelinedRenderingPlugin>()
|
||||
// .disable::<LogPlugin>()
|
||||
// .set({
|
||||
// let mut plugin = WinitPlugin::<WakeUp>::default();
|
||||
// plugin.run_on_any_thread = true;
|
||||
// plugin
|
||||
// });
|
||||
let mut base = (MinimalPlugins)
|
||||
.build()
|
||||
.disable::<ScheduleRunnerPlugin>()
|
||||
.add(TransformPlugin)
|
||||
.add(HierarchyPlugin)
|
||||
.add(InputPlugin)
|
||||
.add(AccessibilityPlugin);
|
||||
base = match headless {
|
||||
true => {
|
||||
base.add(ScheduleRunnerPlugin {
|
||||
// In OpenXR framepacing we trust (else this will eat all of the cpu)
|
||||
run_mode: bevy::app::RunMode::Loop { wait: None },
|
||||
})
|
||||
}
|
||||
false => base.add(WindowPlugin::default()).add({
|
||||
let mut plugin = WinitPlugin::<WakeUp>::default();
|
||||
plugin.run_on_any_thread = true;
|
||||
plugin
|
||||
}),
|
||||
};
|
||||
base = base
|
||||
.add(TerminalCtrlCHandlerPlugin)
|
||||
// might want to modify this in the future?
|
||||
.add(AssetPlugin::default())
|
||||
// will be replaced by bevy_mod_openxr when using OpenXR
|
||||
.add(RenderPlugin::default())
|
||||
.add(ImagePlugin::default())
|
||||
.add(CorePipelinePlugin)
|
||||
// very unsure what is needed here
|
||||
.add(PbrPlugin {
|
||||
// hoping that there is very little overdraw in stardust
|
||||
prepass_enabled: false,
|
||||
add_default_deferred_lighting_plugin: false,
|
||||
use_gpu_instance_buffer_builder: false,
|
||||
})
|
||||
// .add(DummyPbrPlugin)
|
||||
.add(ScenePlugin)
|
||||
.add(GltfPlugin::default())
|
||||
.add(AudioPlugin::default())
|
||||
.add(GizmoPlugin)
|
||||
.add(bevy_sk::vr_materials::SkMaterialPlugin {
|
||||
replace_standard_material: true,
|
||||
})
|
||||
.add(bevy_sk::hand::HandPlugin)
|
||||
.add(SphericalHarmonicsPlugin);
|
||||
|
||||
if args.pipelined_rendering {
|
||||
base = base.add(PipelinedRenderingPlugin);
|
||||
}
|
||||
|
||||
if args.flatscreen {
|
||||
bevy_app.add_plugins(base);
|
||||
} else {
|
||||
bevy_app.add_plugins(
|
||||
add_xr_plugins(base)
|
||||
.set(OxrInitPlugin {
|
||||
app_info: AppInfo {
|
||||
name: "Stardust XR".into(),
|
||||
version: Version(0, 44, 1),
|
||||
},
|
||||
exts: {
|
||||
let mut exts = OxrExtensions::default();
|
||||
exts.enable_hand_tracking();
|
||||
if args.overlay_priority.is_some() {
|
||||
exts.enable_extx_overlay();
|
||||
}
|
||||
exts
|
||||
},
|
||||
blend_modes: Some(vec![
|
||||
openxr::EnvironmentBlendMode::ALPHA_BLEND,
|
||||
openxr::EnvironmentBlendMode::OPAQUE,
|
||||
]),
|
||||
synchronous_pipeline_compilation: false,
|
||||
..Default::default()
|
||||
})
|
||||
.disable::<OxrRenderPlugin>()
|
||||
.disable::<OxrActionSyncingPlugin>()
|
||||
.add_after::<OxrInitPlugin>(StardustOxrRenderPlugin),
|
||||
);
|
||||
if let Some(priority) = args.overlay_priority {
|
||||
bevy_app.insert_resource(OxrOverlaySettings {
|
||||
session_layer_placement: priority,
|
||||
flags: OverlaySessionCreateFlagsEXTX::EMPTY,
|
||||
});
|
||||
}
|
||||
bevy_app.add_event::<OxrSyncActionSet>();
|
||||
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((
|
||||
BevyLinesPlugin,
|
||||
StardustModelPlugin,
|
||||
StardustHandPlugin,
|
||||
StardustTextPlugin,
|
||||
StardustSoundPlugin,
|
||||
StardustControllerPlugin,
|
||||
));
|
||||
load_internal_binary_asset!(
|
||||
bevy_app,
|
||||
Handle::default(),
|
||||
"nodes/drawable/assets/FiraMono-subset.ttf",
|
||||
|bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() }
|
||||
);
|
||||
#[derive(Resource)]
|
||||
struct SkyTexture(Handle<Image>);
|
||||
// Skytex/light stuff
|
||||
bevy_app.add_systems(
|
||||
Startup,
|
||||
move |assests: Res<AssetServer>, mut cmds: Commands| {
|
||||
if let Some(sky) = project_dirs
|
||||
.as_ref()
|
||||
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
|
||||
.filter(|f| f.exists())
|
||||
.map(|p| assests.load(p))
|
||||
{
|
||||
cmds.insert_resource(SkyTexture(sky));
|
||||
}
|
||||
},
|
||||
);
|
||||
#[derive(Resource)]
|
||||
struct RenderBackground(bool);
|
||||
fn update_background(
|
||||
graphics_info: Res<OxrGraphicsInfo>,
|
||||
mut overlay_events: EventReader<OxrOverlaySessionEvent>,
|
||||
mut last_hidden: Local<bool>,
|
||||
cams: Query<Entity, With<Camera3d>>,
|
||||
mut cmds: Commands,
|
||||
) {
|
||||
let env_hidden = graphics_info.blend_mode != openxr::EnvironmentBlendMode::OPAQUE
|
||||
&& overlay_events
|
||||
.read()
|
||||
.last()
|
||||
.map(
|
||||
|OxrOverlaySessionEvent::MainSessionVisibilityChanged { visible, flags: _ }| {
|
||||
*visible
|
||||
},
|
||||
)
|
||||
.unwrap_or(true);
|
||||
if env_hidden && !*last_hidden {
|
||||
cams.iter().for_each(|e| {
|
||||
cmds.entity(e).remove::<Skybox>();
|
||||
});
|
||||
let _span = debug_span!("spawn");
|
||||
cmds.insert_resource(ClearColor(Color::NONE));
|
||||
}
|
||||
*last_hidden = env_hidden;
|
||||
}
|
||||
bevy_app.add_systems(XrSessionCreated, update_background);
|
||||
bevy_app.add_systems(
|
||||
PostUpdate,
|
||||
(|mut objects: ResMut<ServerObjects>,
|
||||
ref_space: Res<XrPrimaryReferenceSpace>,
|
||||
session: Res<OxrSession>| {
|
||||
objects
|
||||
.ref_space
|
||||
.replace(unsafe { ref_space.as_openxr_space(&session) });
|
||||
objects.view_space.replace(
|
||||
session
|
||||
.deref()
|
||||
.deref()
|
||||
.create_reference_space(
|
||||
openxr::ReferenceSpaceType::VIEW,
|
||||
openxr::Posef::IDENTITY,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
})
|
||||
.run_if(on_event::<bevy_mod_xr::session::XrSessionCreatedEvent>),
|
||||
);
|
||||
bevy_app.add_systems(XrPreDestroySession, |mut objetcs: ResMut<ServerObjects>| {
|
||||
objetcs.ref_space = None;
|
||||
objetcs.view_space = None;
|
||||
});
|
||||
bevy_app.add_systems(
|
||||
Update,
|
||||
update_background.run_if(on_event::<OxrOverlaySessionEvent>),
|
||||
);
|
||||
bevy_app.insert_resource(DbusConnection(dbus_connection.clone()));
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
|
||||
#[cfg(feature = "wayland")]
|
||||
@@ -228,62 +450,84 @@ fn stereokit_loop(
|
||||
sk_ready_notifier.notify_waiters();
|
||||
info!("Stardust ready!");
|
||||
|
||||
let mut objects = ServerObjects::new(
|
||||
dbus_connection.clone(),
|
||||
&sk,
|
||||
args.disable_controllers,
|
||||
args.disable_hands,
|
||||
);
|
||||
let objects = ServerObjects::new(dbus_connection.clone());
|
||||
fn sync_sets(session: Res<OxrSession>, mut events: EventReader<OxrSyncActionSet>) {
|
||||
let sets = events
|
||||
.read()
|
||||
.map(|v| &v.0)
|
||||
.map(openxr::ActiveActionSet::new)
|
||||
.collect::<Vec<_>>();
|
||||
if sets.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut last_frame_delta = Duration::ZERO;
|
||||
let mut sleep_duration = Duration::ZERO;
|
||||
while let Some(token) = sk.step() {
|
||||
let _span = debug_span!("StereoKit step");
|
||||
if let Err(err) = session.sync_actions(&sets) {
|
||||
warn!("error while syncing actionsets: {}", err.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
bevy_app.insert_resource(objects);
|
||||
|
||||
fn bevy_step(world: &mut World) {
|
||||
let _span = debug_span!("Bevy step");
|
||||
let _span = _span.enter();
|
||||
|
||||
camera::update(token);
|
||||
// camera::update(token);
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.frame_event();
|
||||
destroy_queue::clear();
|
||||
|
||||
objects.update(&sk, token);
|
||||
let time = world.get_resource_mut::<OxrFrameState>().map(|mut s| {
|
||||
let t = openxr::Time::from_nanos(
|
||||
s.predicted_display_time.as_nanos() + s.predicted_display_period.as_nanos(),
|
||||
);
|
||||
s.predicted_display_time = t;
|
||||
t
|
||||
});
|
||||
world.run_schedule(XrFirst);
|
||||
if world
|
||||
.run_system_cached(openxr_session_running)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
let _ = world.run_system_cached(sync_sets);
|
||||
}
|
||||
let thread = world
|
||||
.run_system_cached(should_run_frame_loop)
|
||||
.unwrap_or(true)
|
||||
.then(|| world.remove_resource::<OxrFrameWaiter>())
|
||||
.flatten()
|
||||
.map(|mut waiter| {
|
||||
TOKIO.spawn_blocking(move || {
|
||||
let _span = debug_span!("frame eeping").entered();
|
||||
let result = waiter.wait();
|
||||
(waiter, result)
|
||||
})
|
||||
});
|
||||
world.run_schedule(InputUpdate);
|
||||
debug_span!("update_objects").in_scope(|| {
|
||||
let session = world.remove_resource::<OxrSession>();
|
||||
let mut objects = world.remove_resource::<ServerObjects>().unwrap();
|
||||
objects.update(session.as_deref(), time);
|
||||
world.insert_resource(objects);
|
||||
if let Some(session) = session {
|
||||
world.insert_resource(session);
|
||||
}
|
||||
});
|
||||
input::process_input();
|
||||
nodes::root::Root::send_frame_events(Time::get_step_unscaled());
|
||||
adaptive_sleep(
|
||||
&mut last_frame_delta,
|
||||
&mut sleep_duration,
|
||||
Duration::from_micros(250),
|
||||
);
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.update();
|
||||
drawable::draw(token);
|
||||
audio::update();
|
||||
}
|
||||
|
||||
info!("Cleanly shut down StereoKit");
|
||||
#[cfg(feature = "wayland")]
|
||||
drop(wayland);
|
||||
}
|
||||
|
||||
fn adaptive_sleep(
|
||||
last_frame_delta: &mut Duration,
|
||||
sleep_duration: &mut Duration,
|
||||
sleep_duration_increase: Duration,
|
||||
) {
|
||||
let frame_delta = Duration::from_secs_f64(Time::get_step_unscaled());
|
||||
if *last_frame_delta < frame_delta {
|
||||
if let Some(frame_delta_delta) = frame_delta.checked_sub(*last_frame_delta) {
|
||||
if let Some(new_sleep_duration) = sleep_duration.checked_sub(frame_delta_delta) {
|
||||
*sleep_duration = new_sleep_duration;
|
||||
nodes::root::Root::send_frame_events(world.resource::<Time>().delta_secs_f64());
|
||||
if let Some((waiter, Ok(state))) = thread.map(|t| TOKIO.block_on(t).unwrap()) {
|
||||
world.insert_resource(OxrFrameState(state));
|
||||
world.insert_resource(waiter);
|
||||
if let Err(err) = world.run_system_cached(update_cameras) {
|
||||
error!("error while running oxr update_cameras system: {err}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*sleep_duration += sleep_duration_increase;
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.update();
|
||||
}
|
||||
bevy_app.add_systems(StardustFirst, bevy_step);
|
||||
let out = bevy_app.run();
|
||||
|
||||
debug_span!("Sleep", ?sleep_duration, ?frame_delta, ?last_frame_delta).in_scope(|| {
|
||||
*last_frame_delta = frame_delta;
|
||||
std::thread::sleep(*sleep_duration); // to give clients a chance to even update anything before drawing
|
||||
});
|
||||
#[cfg(feature = "wayland")]
|
||||
drop(wayland);
|
||||
out
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::{Aspect, Node};
|
||||
use crate::core::{client::Client, registry::Registry};
|
||||
use color_eyre::eyre::Result;
|
||||
use super::{Aspect, AspectIdentifier, Node};
|
||||
use crate::core::{client::Client, error::Result, registry::Registry};
|
||||
use std::{
|
||||
ops::Add,
|
||||
sync::{Arc, Weak},
|
||||
@@ -68,8 +67,31 @@ impl Alias {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Alias {
|
||||
const ID: u64 = 0;
|
||||
}
|
||||
impl Aspect for Alias {
|
||||
const NAME: &'static str = "Alias";
|
||||
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
||||
self
|
||||
}
|
||||
fn run_signal(
|
||||
&self,
|
||||
_calling_client: Arc<Client>,
|
||||
_node: Arc<Node>,
|
||||
_signal: u64,
|
||||
_message: super::Message,
|
||||
) -> Result<(), stardust_xr::scenegraph::ScenegraphError> {
|
||||
Ok(())
|
||||
}
|
||||
fn run_method(
|
||||
&self,
|
||||
_calling_client: Arc<Client>,
|
||||
_node: Arc<Node>,
|
||||
_method: u64,
|
||||
_message: super::Message,
|
||||
_response: crate::core::scenegraph::MethodResponseSender,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_original(node: Arc<Node>, stop_on_disabled: bool) -> Option<Arc<Node>> {
|
||||
@@ -106,7 +128,7 @@ impl AliasList {
|
||||
.into_iter()
|
||||
.find(move |node| links_to(node.clone(), original.clone()))
|
||||
}
|
||||
pub fn get_from_aspect<A: Aspect>(&self, aspect: &A) -> Option<Arc<Node>> {
|
||||
pub fn get_from_aspect<A: AspectIdentifier>(&self, aspect: &A) -> Option<Arc<Node>> {
|
||||
self.0.get_valid_contents().into_iter().find(|node| {
|
||||
let Some(node) = get_original(node.clone(), false) else {
|
||||
return false;
|
||||
@@ -120,7 +142,7 @@ impl AliasList {
|
||||
pub fn get_aliases(&self) -> Vec<Arc<Node>> {
|
||||
self.0.get_valid_contents()
|
||||
}
|
||||
pub fn remove_aspect<A: Aspect>(&self, aspect: &A) {
|
||||
pub fn remove_aspect<A: AspectIdentifier>(&self, aspect: &A) {
|
||||
self.0.retain(|node| {
|
||||
let Some(original) = get_original(node.clone(), false) else {
|
||||
return false;
|
||||
|
||||
@@ -1,109 +1,175 @@
|
||||
use super::{Aspect, Node};
|
||||
use super::{Aspect, AspectIdentifier, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::core::destroy_queue;
|
||||
use crate::core::error::{Result, ServerError};
|
||||
use crate::core::registry::Registry;
|
||||
use crate::core::resource::get_resource_file;
|
||||
use crate::create_interface;
|
||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::spatial::{Spatial, Transform};
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use glam::{vec3, Vec4Swizzles};
|
||||
use crate::nodes::spatial::{Spatial, Transform, SPATIAL_ASPECT_ALIAS_INFO};
|
||||
use bevy::app::{App, Plugin, PostUpdate, PreUpdate};
|
||||
use bevy::asset::AssetServer;
|
||||
use bevy::audio::{AudioPlayer, AudioSink, AudioSinkPlayback, PlaybackSettings, Volume};
|
||||
use bevy::prelude::{Commands, Deref, Entity, Query, Res, Resource, Transform as BevyTransform};
|
||||
use color_eyre::eyre::eyre;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use stardust_xr::values::ResourceID;
|
||||
use tracing::error;
|
||||
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::Arc;
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
use stereokit_rust::sound::{Sound as SkSound, SoundInst};
|
||||
|
||||
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
|
||||
|
||||
pub struct StardustSoundPlugin;
|
||||
impl Plugin for StardustSoundPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
_ = SOUND_EVENT_SENDER.set(tx);
|
||||
app.insert_resource(SoundEventReader(rx));
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
_ = SPAWN_SOUND_SENDER.set(tx);
|
||||
app.insert_resource(SpawnSoundReader(rx));
|
||||
app.add_systems(PostUpdate, update_sound_state);
|
||||
app.add_systems(PreUpdate, spawn_sounds);
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_sounds(reader: Res<SpawnSoundReader>, mut cmds: Commands, asset_server: Res<AssetServer>) {
|
||||
for sound in reader.try_iter() {
|
||||
let e = cmds
|
||||
.spawn((
|
||||
BevyTransform::default(),
|
||||
AudioPlayer::new(asset_server.load(sound.pending_audio_path.as_path())),
|
||||
PlaybackSettings {
|
||||
mode: bevy::audio::PlaybackMode::Once,
|
||||
volume: Volume::new(sound.volume),
|
||||
speed: 1.0,
|
||||
paused: true,
|
||||
spatial: true,
|
||||
spatial_scale: None,
|
||||
},
|
||||
))
|
||||
.id();
|
||||
let _ = sound.entity.set(e);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_sound_state(
|
||||
mut query: Query<&mut AudioSink>,
|
||||
mut transforms: Query<&mut BevyTransform>,
|
||||
mut cmds: Commands,
|
||||
reader: Res<SoundEventReader>,
|
||||
) {
|
||||
for (entity, action) in reader.try_iter() {
|
||||
match action {
|
||||
SoundAction::Stop => {
|
||||
let Ok(sink) = query.get_mut(entity) else {
|
||||
error!("no audio sink to stop?");
|
||||
continue;
|
||||
};
|
||||
sink.stop()
|
||||
}
|
||||
SoundAction::Play => {
|
||||
// Idk let's hope this works?
|
||||
cmds.entity(entity).remove::<AudioSink>();
|
||||
}
|
||||
}
|
||||
}
|
||||
for sound in SOUND_REGISTRY.get_valid_contents() {
|
||||
let Some(entity) = sound.entity.get().copied() else {
|
||||
continue;
|
||||
};
|
||||
let Ok(mut transform) = transforms.get_mut(entity) else {
|
||||
continue;
|
||||
};
|
||||
*transform = BevyTransform::from_matrix(sound.space.global_transform());
|
||||
}
|
||||
}
|
||||
|
||||
stardust_xr_server_codegen::codegen_audio_protocol!();
|
||||
pub struct Sound {
|
||||
space: Arc<Spatial>,
|
||||
|
||||
volume: f32,
|
||||
pending_audio_path: PathBuf,
|
||||
sk_sound: OnceCell<SkSound>,
|
||||
instance: Mutex<Option<SoundInst>>,
|
||||
stop: Mutex<Option<()>>,
|
||||
play: Mutex<Option<()>>,
|
||||
entity: OnceCell<Entity>,
|
||||
}
|
||||
static SPAWN_SOUND_SENDER: OnceCell<crossbeam_channel::Sender<Arc<Sound>>> = OnceCell::new();
|
||||
#[derive(Resource, Deref)]
|
||||
struct SpawnSoundReader(crossbeam_channel::Receiver<Arc<Sound>>);
|
||||
static SOUND_EVENT_SENDER: OnceCell<crossbeam_channel::Sender<(Entity, SoundAction)>> =
|
||||
OnceCell::new();
|
||||
#[derive(Resource, Deref)]
|
||||
struct SoundEventReader(crossbeam_channel::Receiver<(Entity, SoundAction)>);
|
||||
pub enum SoundAction {
|
||||
// Pause and Resume?
|
||||
Stop,
|
||||
Play,
|
||||
}
|
||||
impl Sound {
|
||||
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> {
|
||||
let pending_audio_path = get_resource_file(
|
||||
&resource_id,
|
||||
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
|
||||
&*node.get_client().ok_or(ServerError::NoClient)?,
|
||||
&[OsStr::new("wav"), OsStr::new("mp3")],
|
||||
)
|
||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
||||
.ok_or(ServerError::NoResource)?;
|
||||
let sound = Sound {
|
||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
volume: 1.0,
|
||||
pending_audio_path,
|
||||
sk_sound: OnceCell::new(),
|
||||
instance: Mutex::new(None),
|
||||
stop: Mutex::new(None),
|
||||
play: Mutex::new(None),
|
||||
entity: OnceCell::new(),
|
||||
};
|
||||
let sound_arc = SOUND_REGISTRY.add(sound);
|
||||
node.add_aspect_raw(sound_arc.clone());
|
||||
<Sound as SoundAspect>::add_node_members(node);
|
||||
if let Some(sender) = SPAWN_SOUND_SENDER.get() {
|
||||
sender
|
||||
.send(sound_arc.clone())
|
||||
.map_err(|_| eyre!("Unable to Spawn Audio Node"))?;
|
||||
}
|
||||
Ok(sound_arc)
|
||||
}
|
||||
|
||||
fn update(&self) {
|
||||
let sound = self
|
||||
.sk_sound
|
||||
.get_or_init(|| SkSound::from_file(self.pending_audio_path.clone()).unwrap());
|
||||
if self.stop.lock().take().is_some() {
|
||||
if let Some(instance) = self.instance.lock().take() {
|
||||
instance.stop();
|
||||
}
|
||||
}
|
||||
if self.instance.lock().is_none() && self.play.lock().take().is_some() {
|
||||
let instance = sound.play(vec3(0.0, 0.0, 0.0), Some(self.volume));
|
||||
self.instance.lock().replace(instance);
|
||||
}
|
||||
if let Some(instance) = self.instance.lock().deref_mut() {
|
||||
instance.position(self.space.global_transform().w_axis.xyz());
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Sound {
|
||||
impl_aspect_for_sound_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Sound {
|
||||
const NAME: &'static str = "Sound";
|
||||
impl_aspect_for_sound_aspect! {}
|
||||
}
|
||||
impl SoundAspect for Sound {
|
||||
fn play(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let sound = node.get_aspect::<Sound>().unwrap();
|
||||
sound.play.lock().replace(());
|
||||
if let Some((sender, entity)) = SOUND_EVENT_SENDER
|
||||
.get()
|
||||
.and_then(|s| Some((s, *sound.entity.get()?)))
|
||||
{
|
||||
sender
|
||||
.send((entity, SoundAction::Play))
|
||||
.map_err(|_| eyre!("Unable to Play Audio"))?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn stop(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let sound = node.get_aspect::<Sound>().unwrap();
|
||||
sound.stop.lock().replace(());
|
||||
if let Some((sender, entity)) = SOUND_EVENT_SENDER
|
||||
.get()
|
||||
.and_then(|s| Some((s, *sound.entity.get()?)))
|
||||
{
|
||||
sender
|
||||
.send((entity, SoundAction::Stop))
|
||||
.map_err(|_| eyre!("Unable to Stop Audio"))?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for Sound {
|
||||
fn drop(&mut self) {
|
||||
if let Some(sk_sound) = self.sk_sound.take() {
|
||||
if let Some(sk_sound) = self.entity.take() {
|
||||
destroy_queue::add(sk_sound);
|
||||
}
|
||||
SOUND_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update() {
|
||||
for sound in SOUND_REGISTRY.get_valid_contents() {
|
||||
sound.update()
|
||||
}
|
||||
}
|
||||
|
||||
create_interface!(AudioInterface);
|
||||
struct AudioInterface;
|
||||
impl InterfaceAspect for AudioInterface {
|
||||
impl InterfaceAspect for Interface {
|
||||
#[doc = "Create a sound node. WAV and MP3 are supported."]
|
||||
fn create_sound(
|
||||
_node: Arc<Node>,
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
use super::alias::AliasList;
|
||||
use super::fields::Field;
|
||||
use super::spatial::{parse_transform, Spatial};
|
||||
use super::{Alias, Aspect, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::create_interface;
|
||||
use crate::nodes::fields::FIELD_ALIAS_INFO;
|
||||
use crate::nodes::spatial::Transform;
|
||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||
use color_eyre::eyre::{bail, ensure, eyre, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
|
||||
use stardust_xr::schemas::flex::flexbuffers;
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
|
||||
}
|
||||
|
||||
// TODO: probably just use d-bus for this stuff (custom protocol for exporting spatials as refs) because the mask stuff is just too confusing
|
||||
|
||||
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
|
||||
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
|
||||
|
||||
pub fn get_mask(datamap: &Datamap) -> Result<flexbuffers::MapReader<&[u8]>> {
|
||||
flexbuffers::Reader::get_root(datamap.raw().as_slice())
|
||||
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
|
||||
.get_map()
|
||||
.map_err(|_| eyre!("Mask is not a valid map"))
|
||||
}
|
||||
pub fn mask_matches(mask_map_lesser: &Datamap, mask_map_greater: &Datamap) -> bool {
|
||||
(|| -> Result<_> {
|
||||
for key in get_mask(mask_map_lesser)?.iter_keys() {
|
||||
let lesser_key = get_mask(mask_map_lesser)?.index(key)?;
|
||||
let greater_key = get_mask(mask_map_greater)?.index(key)?;
|
||||
// otherwise zero-length vectors don't count the same as a single type vector
|
||||
if lesser_key.flexbuffer_type().is_heterogenous_vector()
|
||||
&& lesser_key.as_vector().is_empty()
|
||||
&& greater_key.flexbuffer_type().is_vector()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if !lesser_key.flexbuffer_type().is_null()
|
||||
&& lesser_key.flexbuffer_type() != greater_key.flexbuffer_type()
|
||||
{
|
||||
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
stardust_xr_server_codegen::codegen_data_protocol!();
|
||||
|
||||
pub struct PulseSender {
|
||||
node: Weak<Node>,
|
||||
pub mask: Datamap,
|
||||
aliases: AliasList,
|
||||
field_aliases: AliasList,
|
||||
}
|
||||
impl PulseSender {
|
||||
pub fn add_to(node: &Arc<Node>, mask: Datamap) -> Result<Arc<PulseSender>> {
|
||||
let sender = PulseSender {
|
||||
node: Arc::downgrade(node),
|
||||
mask,
|
||||
aliases: AliasList::default(),
|
||||
field_aliases: AliasList::default(),
|
||||
};
|
||||
|
||||
// <PulseSender as PulseSenderAspect>::add_node_members(node);
|
||||
let sender = PULSE_SENDER_REGISTRY.add(sender);
|
||||
node.add_aspect_raw(sender.clone());
|
||||
for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
|
||||
sender.handle_new_receiver(&receiver);
|
||||
}
|
||||
Ok(sender.clone())
|
||||
}
|
||||
fn handle_new_receiver(&self, receiver: &PulseReceiver) {
|
||||
if !mask_matches(&self.mask, &receiver.mask) {
|
||||
return;
|
||||
}
|
||||
let Some(tx_node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(tx_client) = tx_node.get_client() else {
|
||||
return;
|
||||
};
|
||||
let Some(rx_node) = receiver.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
// Receiver itself
|
||||
let Ok(rx_alias) = Alias::create(
|
||||
&rx_node,
|
||||
&tx_client,
|
||||
PULSE_RECEIVER_ASPECT_ALIAS_INFO.clone(),
|
||||
Some(&self.aliases),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Receiver's field
|
||||
let Ok(rx_field_alias) = Alias::create(
|
||||
&rx_node
|
||||
.get_aspect::<PulseReceiver>()
|
||||
.unwrap()
|
||||
.field
|
||||
.spatial
|
||||
.node()
|
||||
.unwrap(),
|
||||
&tx_client,
|
||||
FIELD_ALIAS_INFO.clone(),
|
||||
Some(&self.aliases),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = pulse_sender_client::new_receiver(&tx_node, &rx_alias, &rx_field_alias);
|
||||
}
|
||||
|
||||
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
|
||||
let Some(node) = receiver.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
self.aliases.remove_aspect(receiver);
|
||||
self.field_aliases.remove_aspect(receiver.field.as_ref());
|
||||
let Some(tx_node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let _ = pulse_sender_client::drop_receiver(&tx_node, node.get_id());
|
||||
}
|
||||
}
|
||||
impl Aspect for PulseSender {
|
||||
const NAME: &'static str = "PulseSender";
|
||||
}
|
||||
impl PulseSenderAspect for PulseSender {}
|
||||
impl Drop for PulseSender {
|
||||
fn drop(&mut self) {
|
||||
PULSE_SENDER_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PulseReceiver {
|
||||
pub node: Weak<Node>,
|
||||
pub field: Arc<Field>,
|
||||
pub mask: Datamap,
|
||||
}
|
||||
impl PulseReceiver {
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
field: Arc<Field>,
|
||||
mask: Datamap,
|
||||
) -> Result<Arc<PulseReceiver>> {
|
||||
let receiver = PulseReceiver {
|
||||
node: Arc::downgrade(node),
|
||||
field,
|
||||
mask,
|
||||
};
|
||||
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
|
||||
|
||||
<PulseReceiver as PulseReceiverAspect>::add_node_members(node);
|
||||
node.add_aspect_raw(receiver.clone());
|
||||
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
|
||||
sender.handle_new_receiver(&receiver);
|
||||
}
|
||||
Ok(receiver)
|
||||
}
|
||||
}
|
||||
impl Aspect for PulseReceiver {
|
||||
const NAME: &'static str = "PulseReceiver";
|
||||
}
|
||||
impl PulseReceiverAspect for PulseReceiver {
|
||||
fn send_data(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
sender: Arc<Node>,
|
||||
data: Datamap,
|
||||
) -> Result<()> {
|
||||
let this_receiver = node.get_aspect::<PulseReceiver>().unwrap();
|
||||
|
||||
ensure!(
|
||||
mask_matches(&this_receiver.mask, &data),
|
||||
"Message ({data:?}) does not contain the same keys as the receiver's mask ({:?})",
|
||||
this_receiver.mask
|
||||
);
|
||||
pulse_receiver_client::data(&node, &sender, &data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for PulseReceiver {
|
||||
fn drop(&mut self) {
|
||||
PULSE_RECEIVER_REGISTRY.remove(self);
|
||||
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
|
||||
sender.handle_drop_receiver(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create_interface!(DataInterface);
|
||||
struct DataInterface;
|
||||
impl InterfaceAspect for DataInterface {
|
||||
fn create_pulse_sender(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
mask: Datamap,
|
||||
) -> Result<()> {
|
||||
get_mask(&mask)?;
|
||||
let node = Node::from_id(&calling_client, id, true);
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = transform.to_mat4(true, true, false);
|
||||
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
PulseSender::add_to(&node, mask)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_pulse_receiver(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
field: Arc<Node>,
|
||||
mask: Datamap,
|
||||
) -> Result<()> {
|
||||
get_mask(&mask)?;
|
||||
let node = Node::from_id(&calling_client, id, true);
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = parse_transform(transform, true, true, false);
|
||||
let field = field.get_aspect::<Field>()?;
|
||||
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
PulseReceiver::add_to(&node, field, mask)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn register_keymap(
|
||||
_node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
keymap: String,
|
||||
) -> Result<u64> {
|
||||
let mut keymaps = KEYMAPS.lock();
|
||||
if let Some(found_keymap_id) = keymaps
|
||||
.iter()
|
||||
.filter(|(_k, v)| *v == &keymap)
|
||||
.map(|(k, _v)| k)
|
||||
.last()
|
||||
{
|
||||
return Ok(found_keymap_id.data().as_ffi());
|
||||
}
|
||||
|
||||
let key = keymaps.insert(keymap);
|
||||
Ok(key.data().as_ffi())
|
||||
}
|
||||
|
||||
async fn get_keymap(
|
||||
_node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
keymap_id: u64,
|
||||
) -> Result<String> {
|
||||
let keymaps = KEYMAPS.lock();
|
||||
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else {
|
||||
bail!("Could not find keymap. Try registering it")
|
||||
};
|
||||
|
||||
Ok(keymap.clone())
|
||||
}
|
||||
}
|
||||
BIN
src/nodes/drawable/assets/FiraMono-subset.ttf
Normal file
BIN
src/nodes/drawable/assets/FiraMono-subset.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,16 +1,26 @@
|
||||
use super::{Line, LinesAspect};
|
||||
use crate::core::error::Result;
|
||||
use crate::DefaultMaterial;
|
||||
use crate::{
|
||||
bevy_plugin::{StardustExtract, TemporaryEntity, ViewLocation},
|
||||
core::{client::Client, registry::Registry},
|
||||
nodes::{spatial::Spatial, Aspect, Node},
|
||||
nodes::{spatial::Spatial, Node},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Vec3;
|
||||
use bevy::{
|
||||
app::Plugin,
|
||||
asset::{Assets, RenderAssetUsages},
|
||||
color::{Color, ColorToComponents, Srgba},
|
||||
math::{bounding::Aabb3d, Isometry3d},
|
||||
pbr::MeshMaterial3d,
|
||||
prelude::{
|
||||
AlphaMode, Commands, GlobalTransform, Mesh, Mesh3d, ResMut, Single, Transform, With,
|
||||
},
|
||||
};
|
||||
use glam::{Vec3, Vec3A};
|
||||
use parking_lot::Mutex;
|
||||
use prisma::Lerp;
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
use stereokit_rust::{
|
||||
maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128,
|
||||
};
|
||||
use tracing::{debug_span, info};
|
||||
|
||||
static LINES_REGISTRY: Registry<Lines> = Registry::new();
|
||||
|
||||
@@ -19,76 +29,126 @@ pub struct Lines {
|
||||
data: Mutex<Vec<Line>>,
|
||||
}
|
||||
impl Lines {
|
||||
#[tracing::instrument]
|
||||
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| {
|
||||
let mut bounds = Bounds::default();
|
||||
if let Ok(lines) = node.get_aspect::<Lines>() {
|
||||
for line in &*lines.data.lock() {
|
||||
for point in &line.points {
|
||||
bounds.grown_point(Vec3::from(point.point));
|
||||
}
|
||||
}
|
||||
}
|
||||
bounds
|
||||
});
|
||||
.lock() = {
|
||||
if let Ok(lines) = node.get_aspect::<Lines>() {
|
||||
Aabb3d::from_point_cloud(Isometry3d::IDENTITY, {
|
||||
let _span = debug_span!("add_to data lock").entered();
|
||||
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(),
|
||||
data: Mutex::new(lines),
|
||||
});
|
||||
<Lines as LinesAspect>::add_node_members(node);
|
||||
node.add_aspect_raw(lines.clone());
|
||||
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
fn draw(&self, token: &MainThreadToken) {
|
||||
fn draw(&self, mesh: &mut Mesh, view: &GlobalTransform) -> Transform {
|
||||
let transform_mat = self.space.global_transform();
|
||||
let _span = debug_span!("draw data lock").entered();
|
||||
let data = self.data.lock().clone();
|
||||
drop(_span);
|
||||
let global_to_view = view.compute_matrix().inverse();
|
||||
let local_to_view = transform_mat.inverse() * global_to_view;
|
||||
let view_to_local = local_to_view.inverse();
|
||||
for line in &data {
|
||||
let mut points: VecDeque<SkLinePoint> = line
|
||||
let mut points: VecDeque<BevyLinePoint> = line
|
||||
.points
|
||||
.iter()
|
||||
.map(|p| SkLinePoint {
|
||||
pt: transform_mat.transform_point3(Vec3::from(p.point)).into(),
|
||||
.map(|p| BevyLinePoint {
|
||||
pt: transform_mat.transform_point3(Vec3::from(p.point)),
|
||||
thickness: p.thickness,
|
||||
color: Color128::new(p.color.c.r, p.color.c.g, p.color.c.b, p.color.a).into(),
|
||||
color: Srgba::new(p.color.c.r, p.color.c.g, p.color.c.b, p.color.a),
|
||||
})
|
||||
.collect();
|
||||
if line.cyclic && !points.is_empty() {
|
||||
let first = line.points.first().unwrap();
|
||||
let last = line.points.last().unwrap();
|
||||
|
||||
let color = Color128 {
|
||||
r: first.color.c.r.lerp(&last.color.c.r, 0.5),
|
||||
g: first.color.c.g.lerp(&last.color.c.g, 0.5),
|
||||
b: first.color.c.b.lerp(&last.color.c.b, 0.5),
|
||||
a: first.color.a.lerp(&last.color.a, 0.5),
|
||||
let color = Srgba {
|
||||
red: first.color.c.r.lerp(&last.color.c.r, 0.5),
|
||||
green: first.color.c.g.lerp(&last.color.c.g, 0.5),
|
||||
blue: first.color.c.b.lerp(&last.color.c.b, 0.5),
|
||||
alpha: first.color.a.lerp(&last.color.a, 0.5),
|
||||
};
|
||||
let connect_point = SkLinePoint {
|
||||
pt: transform_mat
|
||||
.transform_point3(Vec3::from(first.point).lerp(Vec3::from(last.point), 0.5))
|
||||
.into(),
|
||||
let connect_point = BevyLinePoint {
|
||||
pt: transform_mat.transform_point3(
|
||||
Vec3::from(first.point).lerp(Vec3::from(last.point), 0.5),
|
||||
),
|
||||
thickness: (first.thickness + last.thickness) * 0.5,
|
||||
color: color.into(),
|
||||
color,
|
||||
};
|
||||
points.push_front(connect_point);
|
||||
points.push_back(connect_point);
|
||||
}
|
||||
stereokit_rust::system::Lines::add_list(token, points.make_contiguous());
|
||||
let mut last_points: Option<(Vec3A, Vec3, Srgba)> = None;
|
||||
let mut vertecies: Vec<[f32; 3]> = Vec::new();
|
||||
let mut colors: Vec<[f32; 4]> = Vec::new();
|
||||
let mut normals: Vec<[f32; 3]> = Vec::new();
|
||||
for point in points.into_iter() {
|
||||
let pt_view = local_to_view.transform_point3a(point.pt.into());
|
||||
let point1_view = pt_view + (Vec3A::Y * (point.thickness / 2.0));
|
||||
let point2_view = pt_view + (Vec3A::NEG_Y * (point.thickness / 2.0));
|
||||
let point1 = view_to_local.transform_point3a(point1_view);
|
||||
let point2 = view_to_local.transform_point3a(point2_view);
|
||||
if let Some((last1, last2, last_color)) = last_points.take() {
|
||||
let normal = view_to_local.transform_vector3a(Vec3A::Z).to_array();
|
||||
for _ in 0..6 {
|
||||
normals.push(normal);
|
||||
}
|
||||
vertecies.push(last1.to_array());
|
||||
vertecies.push(point1.to_array());
|
||||
vertecies.push(last2.to_array());
|
||||
|
||||
vertecies.push(last2.to_array());
|
||||
vertecies.push(point1.to_array());
|
||||
vertecies.push(point2.to_array());
|
||||
|
||||
colors.push(last_color.to_f32_array());
|
||||
colors.push(point.color.to_f32_array());
|
||||
colors.push(last_color.to_f32_array());
|
||||
|
||||
colors.push(last_color.to_f32_array());
|
||||
colors.push(point.color.to_f32_array());
|
||||
colors.push(point.color.to_f32_array());
|
||||
}
|
||||
}
|
||||
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertecies);
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
|
||||
}
|
||||
GlobalTransform::from(transform_mat).into()
|
||||
}
|
||||
}
|
||||
impl Aspect for Lines {
|
||||
const NAME: &'static str = "Lines";
|
||||
#[derive(Clone, Copy)]
|
||||
struct BevyLinePoint {
|
||||
pt: Vec3,
|
||||
color: Srgba,
|
||||
thickness: f32,
|
||||
}
|
||||
impl LinesAspect for Lines {
|
||||
#[tracing::instrument]
|
||||
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
|
||||
let lines_aspect = node.get_aspect::<Lines>()?;
|
||||
let _span = debug_span!("set_lines data lock").entered();
|
||||
*lines_aspect.data.lock() = lines;
|
||||
Ok(())
|
||||
}
|
||||
@@ -99,12 +159,57 @@ impl Drop for Lines {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_all(token: &MainThreadToken) {
|
||||
for lines in LINES_REGISTRY.get_valid_contents() {
|
||||
pub fn draw_all(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<DefaultMaterial>>,
|
||||
mut cmds: Commands,
|
||||
hmd: Option<Single<&GlobalTransform, With<ViewLocation>>>,
|
||||
) {
|
||||
let Some(hmd) = hmd else { return };
|
||||
let material = DefaultMaterial {
|
||||
color: Color::WHITE,
|
||||
alpha_mode: AlphaMode::Blend,
|
||||
..Default::default()
|
||||
};
|
||||
let mat_handle = materials.add(material);
|
||||
let _span = debug_span!("line registry get valid contents").entered();
|
||||
let vec = LINES_REGISTRY.get_valid_contents();
|
||||
drop(_span);
|
||||
info!("len {}", vec.len());
|
||||
for lines in vec {
|
||||
let _span = debug_span!("outer if span").entered();
|
||||
if let Some(node) = lines.space.node() {
|
||||
if node.enabled() {
|
||||
lines.draw(token);
|
||||
let _span = debug_span!("lines_data lock").entered();
|
||||
let w = lines.data.lock().is_empty();
|
||||
drop(_span);
|
||||
let _span = debug_span!("node enabled check").entered();
|
||||
let e = node.enabled();
|
||||
drop(_span);
|
||||
let _span = debug_span!("if span").entered();
|
||||
if e && !w {
|
||||
let _span = debug_span!("create line mesh").entered();
|
||||
info!("spawning line");
|
||||
// Does this rebuild the mesh every frame? yes, is this problematic? probably,
|
||||
// would a shader work better? yes, do i care? not right now
|
||||
let mut mesh = Mesh::new(
|
||||
bevy::render::mesh::PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::RENDER_WORLD,
|
||||
);
|
||||
let transform = lines.draw(&mut mesh, &hmd);
|
||||
let mesh_handle = meshes.add(mesh);
|
||||
cmds.spawn((
|
||||
Mesh3d(mesh_handle),
|
||||
MeshMaterial3d(mat_handle.clone()),
|
||||
TemporaryEntity,
|
||||
transform,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct BevyLinesPlugin;
|
||||
impl Plugin for BevyLinesPlugin {
|
||||
fn build(&self, app: &mut bevy::prelude::App) {
|
||||
app.add_systems(StardustExtract, draw_all);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,57 @@
|
||||
pub mod lines;
|
||||
pub mod model;
|
||||
#[cfg(feature = "wayland")]
|
||||
pub mod shader_manipulation;
|
||||
pub mod shaders;
|
||||
pub mod text;
|
||||
|
||||
use self::{lines::Lines, model::Model, text::Text};
|
||||
use super::{
|
||||
spatial::{Spatial, Transform},
|
||||
Node,
|
||||
Aspect, AspectIdentifier, Node,
|
||||
};
|
||||
use crate::core::{
|
||||
client::Client,
|
||||
error::{Result, ServerError},
|
||||
resource::get_resource_file,
|
||||
};
|
||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||
use crate::{
|
||||
core::{client::Client, resource::get_resource_file},
|
||||
create_interface,
|
||||
};
|
||||
use color_eyre::eyre::{self, Result};
|
||||
use model::ModelPart;
|
||||
use parking_lot::Mutex;
|
||||
use stardust_xr::values::ResourceID;
|
||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||
use stereokit_rust::{sk::MainThreadToken, system::Renderer, tex::SHCubemap};
|
||||
|
||||
// #[instrument(level = "debug", skip(sk))]
|
||||
pub fn draw(token: &MainThreadToken) {
|
||||
lines::draw_all(token);
|
||||
model::draw_all(token);
|
||||
text::draw_all(token);
|
||||
|
||||
if let Some(skytex) = QUEUED_SKYTEX.lock().take() {
|
||||
if let Ok(skytex) = SHCubemap::from_cubemap_equirectangular(skytex, true, 100) {
|
||||
Renderer::skytex(skytex.tex);
|
||||
}
|
||||
}
|
||||
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
|
||||
if let Ok(skylight) = SHCubemap::from_cubemap_equirectangular(skylight, true, 100) {
|
||||
Renderer::skylight(skylight.sh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||
|
||||
stardust_xr_server_codegen::codegen_drawable_protocol!();
|
||||
create_interface!(DrawableInterface);
|
||||
|
||||
pub struct DrawableInterface;
|
||||
impl InterfaceAspect for DrawableInterface {
|
||||
impl AspectIdentifier for Lines {
|
||||
impl_aspect_for_lines_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Lines {
|
||||
impl_aspect_for_lines_aspect! {}
|
||||
}
|
||||
impl AspectIdentifier for Model {
|
||||
impl_aspect_for_model_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Model {
|
||||
impl_aspect_for_model_aspect! {}
|
||||
}
|
||||
impl AspectIdentifier for ModelPart {
|
||||
impl_aspect_for_model_part_aspect_id! {}
|
||||
}
|
||||
impl Aspect for ModelPart {
|
||||
impl_aspect_for_model_part_aspect! {}
|
||||
}
|
||||
impl AspectIdentifier for Text {
|
||||
impl_aspect_for_text_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Text {
|
||||
impl_aspect_for_text_aspect! {}
|
||||
}
|
||||
|
||||
impl InterfaceAspect for Interface {
|
||||
fn set_sky_tex(_node: Arc<Node>, calling_client: Arc<Client>, tex: ResourceID) -> Result<()> {
|
||||
let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
|
||||
.ok_or(eyre::eyre!("Could not find resource"))?;
|
||||
.ok_or(ServerError::NoResource)?;
|
||||
QUEUED_SKYTEX.lock().replace(resource_path);
|
||||
Ok(())
|
||||
}
|
||||
@@ -60,7 +62,7 @@ impl InterfaceAspect for DrawableInterface {
|
||||
light: ResourceID,
|
||||
) -> Result<()> {
|
||||
let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")])
|
||||
.ok_or(eyre::eyre!("Could not find resource"))?;
|
||||
.ok_or(ServerError::NoResource)?;
|
||||
QUEUED_SKYLIGHT.lock().replace(resource_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,285 +1,391 @@
|
||||
use super::{MaterialParameter, ModelAspect, ModelPartAspect, MODEL_PART_ASPECT_ALIAS_INFO};
|
||||
use crate::bevy_plugin::DESTROY_ENTITY;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::error::{Result, ServerError};
|
||||
use crate::core::registry::Registry;
|
||||
use crate::core::resource::get_resource_file;
|
||||
use crate::nodes::alias::{Alias, AliasList};
|
||||
use crate::nodes::spatial::Spatial;
|
||||
use crate::nodes::{Aspect, Node};
|
||||
use color_eyre::eyre::{bail, eyre, Result};
|
||||
use crate::nodes::Node;
|
||||
use crate::DefaultMaterial;
|
||||
use bevy::app::{Plugin, PostUpdate, PreUpdate, Update};
|
||||
use bevy::asset::{AssetServer, Assets, Handle};
|
||||
use bevy::color::{Color, LinearRgba, Srgba};
|
||||
use bevy::core::Name;
|
||||
use bevy::gltf::GltfAssetLabel;
|
||||
use bevy::image::Image;
|
||||
use bevy::math::bounding::Aabb3d;
|
||||
use bevy::pbr::MeshMaterial3d;
|
||||
use bevy::prelude::{
|
||||
AlphaMode, BuildChildrenTransformExt, Children, Commands, Component, Deref, Entity, Has,
|
||||
HierarchyQueryExt, Parent, Query, Res, ResMut, Resource, Transform, Visibility, With, Without,
|
||||
};
|
||||
use bevy::reflect::GetField;
|
||||
use bevy::render::primitives::Aabb;
|
||||
use bevy::scene::SceneRoot;
|
||||
use glam::{Mat4, Vec2, Vec3};
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use stardust_xr::values::ResourceID;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Weak};
|
||||
use stereokit_rust::material::Transparency;
|
||||
use stereokit_rust::maths::Bounds;
|
||||
use stereokit_rust::sk::MainThreadToken;
|
||||
use stereokit_rust::{material::Material, model::Model as SKModel, tex::Tex, util::Color128};
|
||||
|
||||
pub struct MaterialWrapper(pub Material);
|
||||
impl Hash for MaterialWrapper {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.get_shader().0.as_ptr().hash(state);
|
||||
for param in self.0.get_all_param_info() {
|
||||
param.to_string().hash(state)
|
||||
}
|
||||
self.0.get_chain().map(MaterialWrapper).hash(state)
|
||||
}
|
||||
}
|
||||
impl PartialEq for MaterialWrapper {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.0.get_shader().0.as_ptr() != other.0.get_shader().0.as_ptr() {
|
||||
return false;
|
||||
}
|
||||
if self.0.get_all_param_info().count() != other.0.get_all_param_info().count() {
|
||||
return false;
|
||||
}
|
||||
for self_param in self.0.get_all_param_info() {
|
||||
let Some(other_param) = other
|
||||
.0
|
||||
.get_all_param_info()
|
||||
.get_data(self_param.get_name(), self_param.get_type())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if self_param.to_string() != other_param.to_string() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
self.0.get_chain().map(MaterialWrapper) == other.0.get_chain().map(MaterialWrapper)
|
||||
}
|
||||
}
|
||||
impl Eq for MaterialWrapper {}
|
||||
unsafe impl Send for MaterialWrapper {}
|
||||
unsafe impl Sync for MaterialWrapper {}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MaterialRegistry(Mutex<FxHashMap<u64, String>>);
|
||||
impl MaterialRegistry {
|
||||
fn add_or_get(&self, material: Arc<MaterialWrapper>) -> Arc<MaterialWrapper> {
|
||||
let mut lock = self.0.lock();
|
||||
let hash = {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
material.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
|
||||
if let Some(id) = lock.get(&hash) {
|
||||
if let Ok(existing) = Material::find(id) {
|
||||
return Arc::new(MaterialWrapper(existing));
|
||||
}
|
||||
}
|
||||
|
||||
lock.insert(hash, material.0.get_id().to_string());
|
||||
material
|
||||
}
|
||||
}
|
||||
|
||||
static MATERIAL_REGISTRY: Lazy<MaterialRegistry> = Lazy::new(MaterialRegistry::default);
|
||||
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
||||
static HOLDOUT_MATERIAL: OnceCell<Arc<MaterialWrapper>> = OnceCell::new();
|
||||
static HOLDOUT_MATERIAL: OnceCell<Arc<DefaultMaterial>> = OnceCell::new();
|
||||
|
||||
impl MaterialParameter {
|
||||
fn apply_to_material(&self, client: &Client, material: &Material, parameter_name: &str) {
|
||||
let mut params = material.get_all_param_info();
|
||||
fn apply_to_material(
|
||||
&self,
|
||||
client: &Client,
|
||||
material: &mut DefaultMaterial,
|
||||
parameter_name: &str,
|
||||
asset_server: &AssetServer,
|
||||
) {
|
||||
match self {
|
||||
MaterialParameter::Bool(val) => {
|
||||
params.set_bool(parameter_name, *val);
|
||||
let name = parameter_name;
|
||||
if let Some(field) = material.get_field_mut::<bool>(name) {
|
||||
*field = *val;
|
||||
} else {
|
||||
warn!("unknown bool material parameter name: {name}");
|
||||
}
|
||||
}
|
||||
MaterialParameter::Int(val) => {
|
||||
params.set_int(parameter_name, &[*val]);
|
||||
if let Some(field) = material.get_field_mut::<i32>(parameter_name) {
|
||||
*field = *val;
|
||||
} else {
|
||||
warn!("unknown i32 material parameter name: {parameter_name}");
|
||||
}
|
||||
}
|
||||
MaterialParameter::UInt(val) => {
|
||||
params.set_uint(parameter_name, &[*val]);
|
||||
}
|
||||
MaterialParameter::Float(val) => {
|
||||
params.set_float(parameter_name, *val);
|
||||
if let Some(field) = material.get_field_mut::<u32>(parameter_name) {
|
||||
*field = *val;
|
||||
} else {
|
||||
warn!("unknown u32 material parameter name: {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 => {
|
||||
if let Some(field) = material.get_field_mut::<f32>(name) {
|
||||
*field = *val;
|
||||
} else {
|
||||
warn!("unknown f32 material parameter name: {name}");
|
||||
}
|
||||
}
|
||||
},
|
||||
MaterialParameter::Vec2(val) => {
|
||||
params.set_vec2(parameter_name, Vec2::from(*val));
|
||||
if let Some(field) = material.get_field_mut::<Vec2>(parameter_name) {
|
||||
*field = (*val).into();
|
||||
} else {
|
||||
warn!("unknown vec2 material parameter name: {parameter_name}");
|
||||
}
|
||||
}
|
||||
MaterialParameter::Vec3(val) => {
|
||||
params.set_vec3(parameter_name, Vec3::from(*val));
|
||||
}
|
||||
MaterialParameter::Color(val) => {
|
||||
params.set_color(
|
||||
parameter_name,
|
||||
Color128::new(val.c.r, val.c.g, val.c.b, val.a),
|
||||
);
|
||||
if let Some(field) = material.get_field_mut::<Vec3>(parameter_name) {
|
||||
*field = (*val).into();
|
||||
} else {
|
||||
warn!("unknown vec3 material parameter name: {parameter_name}");
|
||||
}
|
||||
}
|
||||
MaterialParameter::Color(val) => match parameter_name {
|
||||
"color" => {
|
||||
material.color = Srgba::new(val.c.r, val.c.g, val.c.b, val.a).into()
|
||||
}
|
||||
"emission_factor" => {
|
||||
material.emission_factor = LinearRgba::new(val.c.r, val.c.g, val.c.b, val.a).into()
|
||||
}
|
||||
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();
|
||||
} else {
|
||||
warn!("unknown color material parameter name: {name}");
|
||||
}
|
||||
}
|
||||
},
|
||||
MaterialParameter::Texture(resource) => {
|
||||
let Some(texture_path) =
|
||||
get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")])
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Ok(tex) = Tex::from_file(texture_path, true, None) {
|
||||
params.set_texture(parameter_name, &tex);
|
||||
let image = asset_server.load::<Image>(texture_path);
|
||||
match parameter_name {
|
||||
"diffuse" => {
|
||||
material.diffuse_texture.replace(image);
|
||||
}
|
||||
"emission" => {
|
||||
material.emission_texture.replace(image);
|
||||
}
|
||||
"normal" => {
|
||||
error!("TODO: implement Normal Map texture in bevy_sk");
|
||||
// material.n.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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModelPart {
|
||||
id: i32,
|
||||
entity: OnceCell<Entity>,
|
||||
path: String,
|
||||
space: Arc<Spatial>,
|
||||
model: Weak<Model>,
|
||||
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
|
||||
pending_material_replacement: Mutex<Option<Arc<MaterialWrapper>>>,
|
||||
pending_material_replacement: Mutex<Option<Arc<DefaultMaterial>>>,
|
||||
aliases: AliasList,
|
||||
}
|
||||
impl ModelPart {
|
||||
fn create_for_model(model: &Arc<Model>, sk_model: &SKModel) {
|
||||
HOLDOUT_MATERIAL.get_or_init(|| {
|
||||
let mut mat = Material::copy(&Material::unlit());
|
||||
mat.transparency(Transparency::None);
|
||||
mat.color_tint(Color128::BLACK_TRANSPARENT);
|
||||
Arc::new(MaterialWrapper(mat))
|
||||
});
|
||||
|
||||
let nodes = sk_model.get_nodes();
|
||||
for part in nodes.all() {
|
||||
ModelPart::create(model, &part);
|
||||
}
|
||||
#[derive(Component, Clone)]
|
||||
pub struct StardustModel(Weak<Model>);
|
||||
#[derive(Component, Clone)]
|
||||
pub struct UnprocessedModel;
|
||||
pub struct StardustModelPlugin;
|
||||
impl Plugin for StardustModelPlugin {
|
||||
fn build(&self, app: &mut bevy::prelude::App) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
let _ = LOAD_MODEL_SENDER.set(tx);
|
||||
app.insert_resource(LoadModelReader(rx));
|
||||
app.add_systems(Update, create_model_parts_for_loaded_models);
|
||||
app.add_systems(PreUpdate, load_models);
|
||||
app.add_systems(PostUpdate, update_models);
|
||||
app.add_systems(PostUpdate, update_model_parts);
|
||||
}
|
||||
}
|
||||
static LOAD_MODEL_SENDER: OnceCell<crossbeam_channel::Sender<(PathBuf, Arc<Model>)>> =
|
||||
OnceCell::new();
|
||||
#[derive(Resource, Deref)]
|
||||
struct LoadModelReader(crossbeam_channel::Receiver<(PathBuf, Arc<Model>)>);
|
||||
|
||||
fn create(model: &Arc<Model>, part: &stereokit_rust::model::ModelNode) -> Option<Arc<Self>> {
|
||||
let mut parts = model.parts.lock();
|
||||
let parent_part = part
|
||||
.get_parent()
|
||||
.and_then(|part| parts.iter().find(|p| p.id == *part.get_id()));
|
||||
|
||||
let stardust_model_part = model.space.node()?;
|
||||
let client = stardust_model_part.get_client()?;
|
||||
let mut part_path = parent_part
|
||||
.map(|n| n.path.clone() + "/")
|
||||
.unwrap_or_default();
|
||||
part_path += part.get_name().unwrap();
|
||||
|
||||
let node = client.scenegraph.add_node(Node::generate(&client, false));
|
||||
let spatial_parent = parent_part
|
||||
.map(|n| n.space.clone())
|
||||
.unwrap_or_else(|| model.space.clone());
|
||||
|
||||
let local_transform = unsafe { part.get_local_transform().m };
|
||||
let space = Spatial::add_to(
|
||||
&node,
|
||||
Some(spatial_parent),
|
||||
Mat4::from_cols_array(&local_transform),
|
||||
false,
|
||||
);
|
||||
|
||||
let _ = space.bounding_box_calc.set(|node| {
|
||||
let Ok(model_part) = node.get_aspect::<ModelPart>() else {
|
||||
return Bounds::default();
|
||||
};
|
||||
let Some(model) = model_part.model.upgrade() else {
|
||||
return Bounds::default();
|
||||
};
|
||||
let Some(sk_model) = model.sk_model.get() else {
|
||||
return Bounds::default();
|
||||
};
|
||||
let model_nodes = sk_model.get_nodes();
|
||||
let Some(model_node) = model_nodes.get_index(model_part.id) else {
|
||||
return Bounds::default();
|
||||
};
|
||||
let Some(sk_mesh) = model_node.get_mesh() else {
|
||||
return Bounds::default();
|
||||
};
|
||||
sk_mesh.get_bounds()
|
||||
});
|
||||
|
||||
let model_part = Arc::new(ModelPart {
|
||||
id: *part.get_id(),
|
||||
path: part_path,
|
||||
space,
|
||||
model: Arc::downgrade(model),
|
||||
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||
pending_material_replacement: Mutex::new(None),
|
||||
aliases: AliasList::default(),
|
||||
});
|
||||
<ModelPart as ModelPartAspect>::add_node_members(&node);
|
||||
node.add_aspect_raw(model_part.clone());
|
||||
parts.push(model_part.clone());
|
||||
Some(model_part)
|
||||
}
|
||||
|
||||
pub fn replace_material(&self, replacement: Arc<MaterialWrapper>) {
|
||||
let shared_material = MATERIAL_REGISTRY.add_or_get(replacement);
|
||||
self.pending_material_replacement
|
||||
.lock()
|
||||
.replace(shared_material);
|
||||
}
|
||||
/// only to be run on the main thread
|
||||
pub fn replace_material_now(&self, replacement: &Material) {
|
||||
let Some(model) = self.model.upgrade() else {
|
||||
return;
|
||||
fn update_models(mut query: Query<(&StardustModel, &mut Visibility, &mut Transform)>) {
|
||||
for (model, mut vis, mut transform) in query.iter_mut() {
|
||||
let Some(model) = model.0.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
let Some(sk_model) = model.sk_model.get() else {
|
||||
return;
|
||||
};
|
||||
let nodes = sk_model.get_nodes();
|
||||
let Some(mut part) = nodes.get_index(self.id) else {
|
||||
return;
|
||||
};
|
||||
let shared_material =
|
||||
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(replacement.copy())));
|
||||
part.material(&shared_material.0);
|
||||
}
|
||||
|
||||
fn update(&self) {
|
||||
let Some(model) = self.model.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(sk_model) = model.sk_model.get() else {
|
||||
return;
|
||||
};
|
||||
let Some(node) = model.space.node() else {
|
||||
return;
|
||||
};
|
||||
let nodes = sk_model.get_nodes();
|
||||
let Some(mut part) = nodes.get_index(self.id) else {
|
||||
return;
|
||||
};
|
||||
part.model_transform(Spatial::space_to_space_matrix(
|
||||
Some(&self.space),
|
||||
Some(&model.space),
|
||||
));
|
||||
|
||||
let Some(client) = node.get_client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
|
||||
part.material(&material_replacement.0);
|
||||
}
|
||||
|
||||
'mat_params: {
|
||||
let mut material_parameters = self.pending_material_parameters.lock();
|
||||
if !material_parameters.is_empty() {
|
||||
let Some(material) = part.get_material() else {
|
||||
break 'mat_params;
|
||||
};
|
||||
let new_material = material.copy();
|
||||
for (parameter_name, parameter_value) in material_parameters.drain() {
|
||||
parameter_value.apply_to_material(&client, &new_material, ¶meter_name);
|
||||
}
|
||||
|
||||
let shared_material =
|
||||
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(new_material)));
|
||||
part.material(&shared_material.0);
|
||||
*transform = Transform::from_matrix(model.space.global_transform());
|
||||
if let Some(node) = model.space.node() {
|
||||
*vis = match node.enabled() {
|
||||
true => Visibility::Inherited,
|
||||
false => Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Aspect for ModelPart {
|
||||
const NAME: &'static str = "ModelPart";
|
||||
|
||||
fn load_models(rx: Res<LoadModelReader>, mut cmds: Commands, asset_server: Res<AssetServer>) {
|
||||
for (path, model) in rx.try_iter() {
|
||||
let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset(path));
|
||||
let entity = cmds
|
||||
.spawn((
|
||||
SceneRoot(handle),
|
||||
StardustModel(Arc::downgrade(&model)),
|
||||
UnprocessedModel,
|
||||
))
|
||||
.id();
|
||||
let _ = model.entity.set(entity);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_model_parts(
|
||||
models: Query<&StardustModel, Without<UnprocessedModel>>,
|
||||
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||
mut part_query: Query<(
|
||||
&mut Transform,
|
||||
&mut MeshMaterial3d<DefaultMaterial>,
|
||||
&mut Visibility,
|
||||
Has<Parent>,
|
||||
)>,
|
||||
mut cmds: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
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, mut vis, has_parent))) = part
|
||||
.entity
|
||||
.get()
|
||||
.and_then(|e| Some((*e, part_query.get_mut(*e).ok()?)))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if has_parent {
|
||||
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: {
|
||||
let mut material_parameters = part.pending_material_parameters.lock();
|
||||
if !material_parameters.is_empty() {
|
||||
let Some(material) = mats.get(&mat.0) else {
|
||||
break 'mat_params;
|
||||
};
|
||||
|
||||
let mut new_material = material.clone();
|
||||
let Some(client) = part.space.node().and_then(|v| v.get_client()) else {
|
||||
return;
|
||||
};
|
||||
for (parameter_name, parameter_value) in material_parameters.drain() {
|
||||
parameter_value.apply_to_material(
|
||||
&client,
|
||||
&mut new_material,
|
||||
¶meter_name,
|
||||
&asset_server,
|
||||
);
|
||||
}
|
||||
mat.0 = mats.add(new_material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path(
|
||||
entity: Entity,
|
||||
query: &Query<(&Parent, &Name), Without<SceneRoot>>,
|
||||
mut in_vec: Vec<String>,
|
||||
) -> Vec<String> {
|
||||
let Ok((parent, name)) = query.get(entity) else {
|
||||
return in_vec;
|
||||
};
|
||||
in_vec.push(name.to_string());
|
||||
get_path(parent.get(), query, in_vec)
|
||||
}
|
||||
|
||||
fn create_model_parts_for_loaded_models(
|
||||
query: Query<(Entity, &StardustModel), (With<UnprocessedModel>, With<Children>)>,
|
||||
children: Query<&Children>,
|
||||
gltf_model_parts: Query<(Entity, &Transform, &Aabb), Without<SceneRoot>>,
|
||||
name_query: Query<(&Parent, &Name), Without<SceneRoot>>,
|
||||
mut cmds: Commands,
|
||||
) {
|
||||
for (entity, model) in &query {
|
||||
info!("creating parts!");
|
||||
let Some(model) = model.0.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
// let mut parts = model.parts.lock();
|
||||
let mut parts = Vec::<Arc<ModelPart>>::new();
|
||||
for (entity, transform, aabb) in children
|
||||
.iter_descendants_depth_first(entity)
|
||||
.filter_map(|e| gltf_model_parts.get(e).ok())
|
||||
{
|
||||
let mut path_parts = get_path(entity, &name_query, Vec::new());
|
||||
path_parts.remove(0);
|
||||
path_parts.reverse();
|
||||
let part_path = path_parts.join("/");
|
||||
path_parts.pop();
|
||||
let parent_path = path_parts.join("/");
|
||||
let parent_part = parts.iter().find(|v| v.path == parent_path);
|
||||
|
||||
let Some(stardust_model_part) = model.space.node() else {
|
||||
continue;
|
||||
};
|
||||
let Some(client) = stardust_model_part.get_client() else {
|
||||
continue;
|
||||
};
|
||||
let model_part = model
|
||||
.parts
|
||||
.lock()
|
||||
.iter()
|
||||
.find(|v| v.path == part_path)
|
||||
.cloned()
|
||||
.map(|v| {
|
||||
*v.space.bounding_box_calc.lock() = Aabb3d::new(aabb.center, aabb.half_extents);
|
||||
if v.entity.set(entity).is_err() {
|
||||
error!(
|
||||
"trying to set entity for already init model part?!
|
||||
please yell at schmarni if you see this"
|
||||
);
|
||||
};
|
||||
|
||||
if let Err(err) = v.space.set_spatial_parent(Some(
|
||||
parent_part.map(|n| &n.space).unwrap_or_else(|| {
|
||||
info!("model is spatial parent");
|
||||
&model.space
|
||||
}),
|
||||
)) {
|
||||
error!("error setting spatial parent for existing model part: {err}");
|
||||
}
|
||||
v.space.set_local_transform(transform.compute_matrix());
|
||||
info!("not fresh {}", &v.path);
|
||||
v
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let node = client.scenegraph.add_node(Node::generate(&client, false));
|
||||
let spatial_parent =
|
||||
parent_part.map(|n| n.space.clone()).unwrap_or_else(|| {
|
||||
info!("model is spatial parent");
|
||||
model.space.clone()
|
||||
});
|
||||
|
||||
let space = Spatial::add_to(
|
||||
&node,
|
||||
Some(spatial_parent),
|
||||
transform.compute_matrix(),
|
||||
false,
|
||||
);
|
||||
|
||||
*space.bounding_box_calc.lock() = Aabb3d::new(aabb.center, aabb.half_extents);
|
||||
|
||||
let model_part = Arc::new(ModelPart {
|
||||
entity: OnceCell::from(entity),
|
||||
path: part_path,
|
||||
space,
|
||||
model: Arc::downgrade(&model),
|
||||
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||
pending_material_replacement: Mutex::new(None),
|
||||
aliases: AliasList::default(),
|
||||
});
|
||||
node.add_aspect_raw(model_part.clone());
|
||||
info!("fresh {}", &model_part.path);
|
||||
model_part
|
||||
});
|
||||
parts.push(model_part.clone());
|
||||
}
|
||||
cmds.entity(entity).remove::<UnprocessedModel>();
|
||||
info!("created parts! {}", parts.len());
|
||||
*model.parts.lock() = parts;
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelPart {
|
||||
pub fn replace_material(&self, replacement: Arc<DefaultMaterial>) {
|
||||
self.pending_material_replacement
|
||||
.lock()
|
||||
.replace(replacement);
|
||||
}
|
||||
}
|
||||
impl ModelPartAspect for ModelPart {
|
||||
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
|
||||
@@ -309,60 +415,31 @@ impl ModelPartAspect for ModelPart {
|
||||
pub struct Model {
|
||||
space: Arc<Spatial>,
|
||||
_resource_id: ResourceID,
|
||||
sk_model: OnceCell<SKModel>,
|
||||
entity: OnceCell<Entity>,
|
||||
parts: Mutex<Vec<Arc<ModelPart>>>,
|
||||
}
|
||||
impl Model {
|
||||
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
|
||||
let pending_model_path = get_resource_file(
|
||||
&resource_id,
|
||||
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
|
||||
&*node.get_client().ok_or(ServerError::NoClient)?,
|
||||
&[OsStr::new("glb"), OsStr::new("gltf")],
|
||||
)
|
||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
||||
.ok_or(ServerError::NoResource)?;
|
||||
|
||||
let model = Arc::new(Model {
|
||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
_resource_id: resource_id,
|
||||
sk_model: OnceCell::new(),
|
||||
entity: OnceCell::new(),
|
||||
parts: Mutex::new(Vec::default()),
|
||||
});
|
||||
<Model as ModelAspect>::add_node_members(node);
|
||||
MODEL_REGISTRY.add_raw(&model);
|
||||
|
||||
// technically doing this in anything but the main thread isn't a good idea but dangit we need those model nodes ASAP
|
||||
let sk_model = SKModel::copy(SKModel::from_file(
|
||||
pending_model_path.to_str().unwrap(),
|
||||
None,
|
||||
)?);
|
||||
ModelPart::create_for_model(&model, &sk_model);
|
||||
let _ = model.sk_model.set(sk_model);
|
||||
if let Some(sender) = LOAD_MODEL_SENDER.get() {
|
||||
let _ = sender.send((pending_model_path, model.clone()));
|
||||
}
|
||||
node.add_aspect_raw(model.clone());
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
fn draw(&self, token: &MainThreadToken) {
|
||||
let Some(sk_model) = self.sk_model.get() else {
|
||||
return;
|
||||
};
|
||||
let parts = self.parts.lock();
|
||||
for model_node in &*parts {
|
||||
model_node.update();
|
||||
}
|
||||
drop(parts);
|
||||
|
||||
if let Some(node) = self.space.node() {
|
||||
if node.enabled() {
|
||||
sk_model.draw(token, self.space.global_transform(), None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: proper hread safety in stereokit_rust (probably just bind stereokit directly)
|
||||
unsafe impl Send for Model {}
|
||||
unsafe impl Sync for Model {}
|
||||
impl Aspect for Model {
|
||||
const NAME: &'static str = "Model";
|
||||
}
|
||||
impl ModelAspect for Model {
|
||||
#[doc = "Bind a model part to the node with the ID input."]
|
||||
@@ -371,13 +448,37 @@ impl ModelAspect for Model {
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
part_path: String,
|
||||
) -> color_eyre::eyre::Result<()> {
|
||||
) -> Result<()> {
|
||||
let model = node.get_aspect::<Model>()?;
|
||||
let parts = model.parts.lock();
|
||||
let Some(part) = parts.iter().find(|p| p.path == part_path) else {
|
||||
let paths = parts.iter().map(|p| &p.path).collect::<Vec<_>>();
|
||||
bail!("Couldn't find model part at path {part_path}, all available paths: {paths:?}",)
|
||||
};
|
||||
let mut parts = model.parts.lock();
|
||||
let part =
|
||||
parts
|
||||
.iter()
|
||||
.find(|p| p.path == part_path)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
let paths = parts.iter().map(|p| &p.path).collect::<Vec<_>>();
|
||||
error!("Couldn't find model part at path {part_path}, all available paths: {paths:?}");
|
||||
|
||||
let node = calling_client
|
||||
.scenegraph
|
||||
.add_node(Node::generate(&calling_client, false));
|
||||
|
||||
let space = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
|
||||
|
||||
let model_part = Arc::new(ModelPart {
|
||||
entity: OnceCell::new(),
|
||||
path: part_path,
|
||||
space,
|
||||
model: Arc::downgrade(&model),
|
||||
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||
pending_material_replacement: Mutex::new(None),
|
||||
aliases: AliasList::default(),
|
||||
});
|
||||
node.add_aspect_raw(model_part.clone());
|
||||
parts.push(model_part.clone());
|
||||
model_part
|
||||
});
|
||||
Alias::create_with_id(
|
||||
&part.space.node().unwrap(),
|
||||
&calling_client,
|
||||
@@ -388,14 +489,18 @@ impl ModelAspect for Model {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for ModelPart {
|
||||
fn drop(&mut self) {
|
||||
if let Some(e) = self.entity.get() {
|
||||
_ = DESTROY_ENTITY.send(*e);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for Model {
|
||||
fn drop(&mut self) {
|
||||
if let Some(e) = self.entity.get() {
|
||||
_ = DESTROY_ENTITY.send(*e);
|
||||
}
|
||||
MODEL_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_all(token: &MainThreadToken) {
|
||||
for model in MODEL_REGISTRY.get_valid_contents() {
|
||||
model.draw(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
use smithay::backend::renderer::gles::{
|
||||
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
|
||||
GlesError,
|
||||
};
|
||||
use stereokit_rust::shader::{Shader, _ShaderT};
|
||||
use tracing::error;
|
||||
|
||||
struct FfiAssetHeader {
|
||||
asset_type: i32,
|
||||
asset_state: i32,
|
||||
id: u64,
|
||||
index: u64,
|
||||
refs: i32,
|
||||
debug: *mut u8,
|
||||
}
|
||||
|
||||
struct FfiSkgShader {
|
||||
meta: *mut u8,
|
||||
vertex: u32,
|
||||
pixel: u32,
|
||||
program: u32,
|
||||
compute: u32,
|
||||
}
|
||||
|
||||
struct FfiShader {
|
||||
header: FfiAssetHeader,
|
||||
shader: FfiSkgShader,
|
||||
}
|
||||
|
||||
unsafe fn compile_shader(
|
||||
gl: &ffi::Gles2,
|
||||
variant: ffi::types::GLuint,
|
||||
src: &str,
|
||||
) -> Result<ffi::types::GLuint, GlesError> {
|
||||
let shader = gl.CreateShader(variant);
|
||||
if shader == 0 {
|
||||
return Err(GlesError::CreateShaderObject);
|
||||
}
|
||||
|
||||
gl.ShaderSource(
|
||||
shader,
|
||||
1,
|
||||
&src.as_ptr() as *const *const u8 as *const *const ffi::types::GLchar,
|
||||
&(src.len() as i32) as *const _,
|
||||
);
|
||||
gl.CompileShader(shader);
|
||||
|
||||
let mut status = ffi::FALSE as i32;
|
||||
gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _);
|
||||
if status == ffi::FALSE as i32 {
|
||||
let mut max_len = 0;
|
||||
gl.GetShaderiv(shader, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||
|
||||
let mut error = Vec::with_capacity(max_len as usize);
|
||||
let mut len = 0;
|
||||
gl.GetShaderInfoLog(
|
||||
shader,
|
||||
max_len as _,
|
||||
&mut len as *mut _,
|
||||
error.as_mut_ptr() as *mut _,
|
||||
);
|
||||
error.set_len(len as usize);
|
||||
|
||||
error!(
|
||||
"[GL] {}",
|
||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||
);
|
||||
|
||||
gl.DeleteShader(shader);
|
||||
return Err(GlesError::ShaderCompileError);
|
||||
}
|
||||
|
||||
Ok(shader)
|
||||
}
|
||||
|
||||
unsafe fn link_program(
|
||||
gl: &ffi::Gles2,
|
||||
vert: ffi::types::GLuint,
|
||||
frag: ffi::types::GLuint,
|
||||
) -> Result<ffi::types::GLuint, GlesError> {
|
||||
let program = gl.CreateProgram();
|
||||
gl.AttachShader(program, vert);
|
||||
gl.AttachShader(program, frag);
|
||||
gl.LinkProgram(program);
|
||||
// gl.DetachShader(program, vert);
|
||||
// gl.DetachShader(program, frag);
|
||||
// gl.DeleteShader(vert);
|
||||
// gl.DeleteShader(frag);
|
||||
|
||||
let mut status = ffi::FALSE as i32;
|
||||
gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _);
|
||||
if status == ffi::FALSE as i32 {
|
||||
let mut max_len = 0;
|
||||
gl.GetProgramiv(program, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||
|
||||
let mut error = Vec::with_capacity(max_len as usize);
|
||||
let mut len = 0;
|
||||
gl.GetProgramInfoLog(
|
||||
program,
|
||||
max_len as _,
|
||||
&mut len as *mut _,
|
||||
error.as_mut_ptr() as *mut _,
|
||||
);
|
||||
error.set_len(len as usize);
|
||||
|
||||
error!(
|
||||
"[GL] {}",
|
||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||
);
|
||||
|
||||
gl.DeleteProgram(program);
|
||||
return Err(GlesError::ProgramLinkError);
|
||||
}
|
||||
|
||||
Ok(program)
|
||||
}
|
||||
|
||||
pub unsafe fn shader_inject(
|
||||
c: &Gles2,
|
||||
sk_shader: &mut Shader,
|
||||
vert_str: &str,
|
||||
frag_str: &str,
|
||||
) -> Result<(), GlesError> {
|
||||
let gl_vert = compile_shader(c, VERTEX_SHADER, vert_str)?;
|
||||
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
|
||||
let gl_prog = link_program(c, gl_vert, gl_frag)?;
|
||||
|
||||
let shader = sk_shader.0.as_mut() as *mut _ShaderT as *mut FfiShader;
|
||||
if let Some(shader) = shader.as_mut() {
|
||||
shader.shader.vertex = gl_vert;
|
||||
shader.shader.pixel = gl_frag;
|
||||
shader.shader.program = gl_prog;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// Simula shader with fancy lanzcos sampling
|
||||
pub const UNLIT_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_gamma.hlsl.sks");
|
||||
|
||||
// Simula shader with fancy lanzcos sampling
|
||||
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_simula.hlsl.sks");
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "stereokit.hlsli"
|
||||
|
||||
//--name = sk/unlit
|
||||
//--diffuse = white
|
||||
//--uv_offset = 0.0, 0.0
|
||||
//--uv_scale = 1.0, 1.0
|
||||
Texture2D diffuse : register(t0);
|
||||
SamplerState diffuse_s : register(s0);
|
||||
float2 uv_scale;
|
||||
float2 uv_offset;
|
||||
|
||||
struct vsIn {
|
||||
float4 pos : SV_Position;
|
||||
float3 norm : NORMAL0;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
struct psIn {
|
||||
float4 pos : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
uint view_id : SV_RenderTargetArrayIndex;
|
||||
};
|
||||
|
||||
psIn vs(vsIn input, uint id : SV_InstanceID) {
|
||||
psIn o;
|
||||
o.view_id = id % sk_view_count;
|
||||
id = id / sk_view_count;
|
||||
|
||||
float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz;
|
||||
o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
|
||||
|
||||
o.uv = (input.uv + uv_offset) * uv_scale;
|
||||
return o;
|
||||
}
|
||||
float4 ps(psIn input) : SV_TARGET {
|
||||
float4 col = diffuse.Sample(diffuse_s, input.uv);
|
||||
col.rgb = pow(col.rgb, float3(2.2));
|
||||
|
||||
return col;
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
#include "stereokit.hlsli"
|
||||
|
||||
// Port of https://github.com/SimulaVR/Simula/blob/master/addons/godot-haskell-plugin/TextShader.tres to StereoKit and HLSL.
|
||||
|
||||
//--name = stardust/text_shader
|
||||
//--diffuse = white
|
||||
//--uv_offset = 0.0, 0.0
|
||||
//--uv_scale = 1.0, 1.0
|
||||
//--fcFactor = 1.0
|
||||
//--ripple = 4.0
|
||||
//--alpha_min = 0.0
|
||||
//--alpha_max = 1.0
|
||||
Texture2D diffuse : register(t0);
|
||||
SamplerState diffuse_s : register(s0);
|
||||
float4 diffuse_i;
|
||||
float2 uv_scale;
|
||||
float2 uv_offset;
|
||||
float fcFactor;
|
||||
float ripple;
|
||||
float alpha_min;
|
||||
float alpha_max;
|
||||
|
||||
struct vsIn {
|
||||
float4 pos : SV_Position;
|
||||
float3 norm : NORMAL0;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
struct psIn {
|
||||
float4 pos : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
uint view_id : SV_RenderTargetArrayIndex;
|
||||
};
|
||||
|
||||
psIn vs(vsIn input, uint id : SV_InstanceID) {
|
||||
psIn o;
|
||||
o.view_id = id % sk_view_count;
|
||||
id = id / sk_view_count;
|
||||
|
||||
float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz;
|
||||
o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
|
||||
|
||||
o.uv = (input.uv + uv_offset) * uv_scale;
|
||||
return o;
|
||||
}
|
||||
|
||||
float map(float value, float min1, float max1, float min2, float max2) {
|
||||
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
||||
}
|
||||
|
||||
// float gaussian(float x, float t) {
|
||||
// float PI = 3.14159265358;
|
||||
// return exp(-x*x/(2.0 * t*t))/(sqrt(2.0*PI)*t);
|
||||
// }
|
||||
|
||||
float besselI0(float x) {
|
||||
return 1.0 + pow(x, 2.0) * (0.25 + pow(x, 2.0) * (0.015625 + pow(x, 2.0) * (0.000434028 + pow(x, 2.0) * (6.78168e-6 + pow(x, 2.0) * (6.78168e-8 + pow(x, 2.0) * (4.7095e-10 + pow(x, 2.0) * (2.40281e-12 + pow(x, 2.0) * (9.38597e-15 + pow(x, 2.0) * (2.8969e-17 + 7.24226e-20 * pow(x, 2.0))))))))));
|
||||
}
|
||||
|
||||
float kaiser(float x, float alpha) {
|
||||
if (x > 1.0) {
|
||||
return 0.0;
|
||||
}
|
||||
return besselI0(alpha * sqrt(1.0-x*x));
|
||||
}
|
||||
|
||||
float4 lowpassFilter(Texture2D tex, sampler2D texSampler, float2 uv, float alpha) {
|
||||
float PI = 3.14159265358;
|
||||
|
||||
float4 q = float4(0.0);
|
||||
|
||||
float2 dx_uv = ddx(uv);
|
||||
float2 dy_uv = ddy(uv);
|
||||
//float width = sqrt(max(dot(dx_uv, dx_uv), dot(dy_uv, dy_uv)));
|
||||
float2 width = abs(float2(dx_uv.x, dy_uv.y));
|
||||
|
||||
float2 pixelWidth = floor(width * diffuse_i.xy);
|
||||
float2 aspectRatio = normalize(pixelWidth);
|
||||
|
||||
float2 xyf = uv * diffuse_i.xy;
|
||||
int2 xy = int2(xyf);
|
||||
|
||||
pixelWidth = clamp(pixelWidth, float2(1.0), float2(2.0));
|
||||
|
||||
int2 start = xy - int2(pixelWidth);
|
||||
int2 end = xy + int2(pixelWidth);
|
||||
|
||||
float4 outColor = float4(0.0);
|
||||
|
||||
float qSum = 0.0;
|
||||
|
||||
for (int v = start.y; v <= end.y; v++) {
|
||||
for (int u = start.x; u <= end.x; u++) {
|
||||
float kx = fcFactor * (xyf.x - float(u))/pixelWidth.x;
|
||||
float ky = fcFactor * (xyf.y - float(v))/pixelWidth.y;
|
||||
|
||||
//float lanczosValue = gaussian(kx, fcx);
|
||||
float lanczosValue = kaiser(sqrt(kx*kx + ky*ky), alpha);
|
||||
|
||||
q += tex.Sample(texSampler, (float2(u, v)+float2(0.5))/diffuse_i.xy) * lanczosValue;
|
||||
// q += tex.Load(int3(u, v, 0)) * lanczosValue;
|
||||
qSum += lanczosValue;
|
||||
}
|
||||
}
|
||||
|
||||
return q/qSum;
|
||||
}
|
||||
|
||||
float4 ps(psIn input) : SV_TARGET {
|
||||
float gamma = 2.2;
|
||||
// float4 col = diffuse.Sample(diffuse_s, input.uv);
|
||||
|
||||
// float4 col = lowpassFilter(diffuse, diffuse_s, diffuse_i.xy, float2(1.0 - input.uv.x, input.uv.y), ripple);
|
||||
float4 col = lowpassFilter(diffuse, diffuse_s, input.uv, ripple);
|
||||
// float4 col = diffuse.Sample(diffuse_s, input.uv);
|
||||
col.rgb = pow(col.rgb, float3(gamma));
|
||||
col.a = map(col.a, 0, 1, alpha_min, alpha_max);
|
||||
|
||||
return col;
|
||||
}
|
||||
@@ -1,139 +1,143 @@
|
||||
use crate::{
|
||||
core::{client::Client, destroy_queue, registry::Registry, resource::get_resource_file},
|
||||
nodes::{spatial::Spatial, Aspect, Node},
|
||||
bevy_plugin::{convert_linear_rgba, DESTROY_ENTITY},
|
||||
core::{
|
||||
client::Client,
|
||||
error::{Result, ServerError},
|
||||
registry::Registry,
|
||||
resource::get_resource_file,
|
||||
},
|
||||
nodes::{spatial::Spatial, Node},
|
||||
DefaultMaterial,
|
||||
};
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use glam::{vec3, Mat4, Vec2};
|
||||
use bevy::{
|
||||
app::{App, Plugin, PostUpdate, PreUpdate},
|
||||
asset::{AssetServer, Assets},
|
||||
pbr::MeshMaterial3d,
|
||||
prelude::{Commands, Deref, Entity, Query, Res, ResMut, Resource, Transform},
|
||||
};
|
||||
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 stereokit_rust::{
|
||||
font::Font,
|
||||
sk::MainThreadToken,
|
||||
system::{TextAlign, TextFit, TextStyle as SkTextStyle},
|
||||
util::{Color128, Color32},
|
||||
};
|
||||
use tracing::info_span;
|
||||
|
||||
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,
|
||||
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);
|
||||
app.insert_resource(SpawnTextReader(rx));
|
||||
app.add_systems(PostUpdate, update_text);
|
||||
app.add_systems(PreUpdate, spawn_text);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_text(mut surface_query: Query<&mut Transform>) {
|
||||
for text in TEXT_REGISTRY.get_valid_contents() {
|
||||
let Some(mut transform) = text
|
||||
.entity
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_text(
|
||||
reader: Res<SpawnTextReader>,
|
||||
mut cmds: Commands,
|
||||
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
for text in reader.try_iter() {
|
||||
let _span = info_span!("spawning text").entered();
|
||||
let _span2 = info_span!("text data lock").entered();
|
||||
let data = text.data.lock();
|
||||
drop(_span2);
|
||||
let _span2 = info_span!("text str lock").entered();
|
||||
let str = text.text.lock().clone();
|
||||
drop(_span2);
|
||||
let mat = mats.add(DefaultMaterial {
|
||||
color: convert_linear_rgba(data.color).into(),
|
||||
..Default::default()
|
||||
});
|
||||
let font = text
|
||||
.font_path
|
||||
.as_ref()
|
||||
.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!("setting OneCells").entered();
|
||||
let _ = text.entity.set(entity);
|
||||
}
|
||||
}
|
||||
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>,
|
||||
text: Mutex<Arc<str>>,
|
||||
data: Mutex<TextStyle>,
|
||||
entity: OnceCell<Entity>,
|
||||
}
|
||||
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 client = node.get_client().ok_or(ServerError::NoClient)?;
|
||||
let text = TEXT_REGISTRY.add(Text {
|
||||
space: 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: OnceCell::new(),
|
||||
|
||||
text: Mutex::new(text),
|
||||
text: Mutex::new(text.into()),
|
||||
data: Mutex::new(style),
|
||||
entity: OnceCell::new(),
|
||||
});
|
||||
<Text as TextAspect>::add_node_members(node);
|
||||
node.add_aspect_raw(text.clone());
|
||||
if let Some(sender) = SPAWN_TEXT_SENDER.get() {
|
||||
let _ = sender.send(text.clone());
|
||||
}
|
||||
|
||||
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 Aspect for Text {
|
||||
const NAME: &'static str = "Text";
|
||||
}
|
||||
impl TextAspect for Text {
|
||||
fn set_character_height(
|
||||
@@ -148,25 +152,15 @@ impl TextAspect for Text {
|
||||
|
||||
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;
|
||||
*this_text.text.lock() = text.into();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for Text {
|
||||
fn drop(&mut self) {
|
||||
if let Some(style) = self.style.take() {
|
||||
destroy_queue::add(style);
|
||||
if let Some(e) = self.entity.get() {
|
||||
let _ = DESTROY_ENTITY.send(*e);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ use super::spatial::{
|
||||
Spatial, SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
|
||||
SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
|
||||
};
|
||||
use super::{Aspect, Node};
|
||||
use super::{Aspect, AspectIdentifier, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::create_interface;
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::spatial::Transform;
|
||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||
use color_eyre::eyre::{OptionExt, Result};
|
||||
use color_eyre::eyre::OptionExt;
|
||||
use glam::{vec2, vec3, vec3a, Vec3, Vec3A, Vec3Swizzles};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
@@ -145,15 +145,65 @@ impl Field {
|
||||
shape: Mutex::new(shape),
|
||||
};
|
||||
let field = node.add_aspect(field);
|
||||
<Field as FieldRefAspect>::add_node_members(node);
|
||||
<Field as FieldAspect>::add_node_members(node);
|
||||
node.add_aspect(FieldRef);
|
||||
Ok(field)
|
||||
}
|
||||
}
|
||||
impl Aspect for Field {
|
||||
const NAME: &'static str = "Field";
|
||||
impl AspectIdentifier for Field {
|
||||
impl_aspect_for_field_aspect_id! {}
|
||||
}
|
||||
impl FieldRefAspect for Field {
|
||||
impl Aspect for Field {
|
||||
impl_aspect_for_field_aspect! {}
|
||||
}
|
||||
impl FieldAspect for Field {
|
||||
fn set_shape(node: Arc<Node>, _calling_client: Arc<Client>, shape: Shape) -> Result<()> {
|
||||
let field = node.get_aspect::<Field>()?;
|
||||
*field.shape.lock() = shape;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn export_field(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
|
||||
let id = rand::random();
|
||||
EXPORTED_FIELDS.lock().insert(id, node);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
impl FieldTrait for Field {
|
||||
fn spatial_ref(&self) -> &Spatial {
|
||||
&self.spatial
|
||||
}
|
||||
fn local_distance(&self, p: Vec3A) -> f32 {
|
||||
match self.shape.lock().clone() {
|
||||
Shape::Box(size) => {
|
||||
let q = vec3(
|
||||
p.x.abs() - (size.x * 0.5_f32),
|
||||
p.y.abs() - (size.y * 0.5_f32),
|
||||
p.z.abs() - (size.z * 0.5_f32),
|
||||
);
|
||||
let v = vec3a(q.x.max(0_f32), q.y.max(0_f32), q.z.max(0_f32));
|
||||
v.length() + q.x.max(q.y.max(q.z)).min(0_f32)
|
||||
}
|
||||
Shape::Cylinder(CylinderShape { length, radius }) => {
|
||||
let d = vec2(p.xy().length().abs() - radius, p.z.abs() - (length * 0.5));
|
||||
d.x.max(d.y).min(0.0) + d.max(vec2(0.0, 0.0)).length()
|
||||
}
|
||||
Shape::Sphere(radius) => p.length() - radius,
|
||||
Shape::Torus(TorusShape { radius_a, radius_b }) => {
|
||||
let q = vec2(p.xz().length() - radius_a, p.y);
|
||||
q.length() - radius_b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldRef;
|
||||
impl AspectIdentifier for FieldRef {
|
||||
impl_aspect_for_field_ref_aspect_id! {}
|
||||
}
|
||||
impl Aspect for FieldRef {
|
||||
impl_aspect_for_field_ref_aspect! {}
|
||||
}
|
||||
impl FieldRefAspect for FieldRef {
|
||||
async fn distance(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
@@ -205,56 +255,14 @@ impl FieldRefAspect for Field {
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl FieldAspect for Field {
|
||||
fn set_shape(node: Arc<Node>, _calling_client: Arc<Client>, shape: Shape) -> Result<()> {
|
||||
let field = node.get_aspect::<Field>()?;
|
||||
*field.shape.lock() = shape;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn export_field(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
|
||||
let id = rand::random();
|
||||
EXPORTED_FIELDS.lock().insert(id, node);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
impl FieldTrait for Field {
|
||||
fn spatial_ref(&self) -> &Spatial {
|
||||
&self.spatial
|
||||
}
|
||||
fn local_distance(&self, p: Vec3A) -> f32 {
|
||||
match self.shape.lock().clone() {
|
||||
Shape::Box(size) => {
|
||||
let q = vec3(
|
||||
p.x.abs() - (size.x * 0.5_f32),
|
||||
p.y.abs() - (size.y * 0.5_f32),
|
||||
p.z.abs() - (size.z * 0.5_f32),
|
||||
);
|
||||
let v = vec3a(q.x.max(0_f32), q.y.max(0_f32), q.z.max(0_f32));
|
||||
v.length() + q.x.max(q.y.max(q.z)).min(0_f32)
|
||||
}
|
||||
Shape::Cylinder(CylinderShape { length, radius }) => {
|
||||
let d = vec2(p.xy().length().abs() - radius, p.z.abs() - (length * 0.5));
|
||||
d.x.max(d.y).min(0.0) + d.max(vec2(0.0, 0.0)).length()
|
||||
}
|
||||
Shape::Sphere(radius) => p.length() - radius,
|
||||
Shape::Torus(TorusShape { radius_a, radius_b }) => {
|
||||
let q = vec2(p.xz().length() - radius_a, p.y);
|
||||
q.length() - radius_b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create_interface!(FieldInterface);
|
||||
pub struct FieldInterface;
|
||||
impl InterfaceAspect for FieldInterface {
|
||||
impl InterfaceAspect for Interface {
|
||||
async fn import_field_ref(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
uid: u64,
|
||||
) -> Result<Arc<Node>> {
|
||||
EXPORTED_FIELDS
|
||||
Ok(EXPORTED_FIELDS
|
||||
.lock()
|
||||
.get(&uid)
|
||||
.map(|s| {
|
||||
@@ -266,7 +274,7 @@ impl InterfaceAspect for FieldInterface {
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.ok_or_eyre("Couldn't find spatial with that ID")
|
||||
.ok_or_eyre("Couldn't find spatial with that ID")?)
|
||||
}
|
||||
|
||||
fn create_field(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{InputHandlerAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REGISTRY};
|
||||
use crate::nodes::{alias::AliasList, fields::Field, spatial::Spatial, Aspect, Node};
|
||||
use crate::nodes::{alias::AliasList, fields::Field, spatial::Spatial, Node};
|
||||
use color_eyre::eyre::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -23,9 +23,6 @@ impl InputHandler {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Aspect for InputHandler {
|
||||
const NAME: &'static str = "InputHandler";
|
||||
}
|
||||
impl InputHandlerAspect for InputHandler {}
|
||||
impl PartialEq for InputHandler {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
|
||||
@@ -4,15 +4,14 @@ use super::{
|
||||
INPUT_METHOD_REGISTRY,
|
||||
};
|
||||
use crate::{
|
||||
core::{client::Client, registry::Registry},
|
||||
core::{client::Client, error::Result, registry::Registry},
|
||||
nodes::{
|
||||
alias::{Alias, AliasList},
|
||||
fields::{Field, FIELD_ALIAS_INFO},
|
||||
spatial::Spatial,
|
||||
Aspect, Node,
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use parking_lot::Mutex;
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::{Arc, Weak};
|
||||
@@ -45,13 +44,12 @@ impl InputMethod {
|
||||
internal_capture_requests: Registry::new(),
|
||||
captures: Registry::new(),
|
||||
};
|
||||
<InputMethod as InputMethodRefAspect>::add_node_members(node);
|
||||
<InputMethod as InputMethodAspect>::add_node_members(node);
|
||||
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
|
||||
method.handle_new_handler(&handler);
|
||||
}
|
||||
let method = INPUT_METHOD_REGISTRY.add(method);
|
||||
node.add_aspect_raw(method.clone());
|
||||
node.add_aspect(InputMethodRef);
|
||||
Ok(method)
|
||||
}
|
||||
|
||||
@@ -154,25 +152,6 @@ impl InputMethod {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Aspect for InputMethod {
|
||||
const NAME: &'static str = "InputMethod";
|
||||
}
|
||||
impl InputMethodRefAspect for InputMethod {
|
||||
#[doc = "Have the input handler that this method reference came from capture the method for the next frame."]
|
||||
fn request_capture(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
handler: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
let input_method = node.get_aspect::<InputMethod>()?;
|
||||
let input_handler = handler.get_aspect::<InputHandler>()?;
|
||||
|
||||
input_method
|
||||
.internal_capture_requests
|
||||
.add_raw(&input_handler);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl InputMethodAspect for InputMethod {
|
||||
#[doc = "Set the spatial input component of this input method. You must keep the same input data type throughout the entire thing."]
|
||||
fn set_input(
|
||||
@@ -231,3 +210,21 @@ impl Drop for InputMethod {
|
||||
INPUT_METHOD_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputMethodRef;
|
||||
impl InputMethodRefAspect for InputMethodRef {
|
||||
#[doc = "Have the input handler that this method reference came from capture the method for the next frame."]
|
||||
fn request_capture(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
handler: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
let input_method = node.get_aspect::<InputMethod>()?;
|
||||
let input_handler = handler.get_aspect::<InputHandler>()?;
|
||||
|
||||
input_method
|
||||
.internal_capture_requests
|
||||
.add_raw(&input_handler);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,17 @@ mod tip;
|
||||
|
||||
pub use handler::*;
|
||||
pub use method::*;
|
||||
use tracing::debug_span;
|
||||
|
||||
use super::fields::Field;
|
||||
use super::spatial::Spatial;
|
||||
use crate::create_interface;
|
||||
use super::Aspect;
|
||||
use super::AspectIdentifier;
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||
use crate::{core::client::Client, nodes::Node};
|
||||
use crate::{core::registry::Registry, nodes::spatial::Transform};
|
||||
use color_eyre::eyre::Result;
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -25,6 +27,25 @@ pub static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
|
||||
|
||||
stardust_xr_server_codegen::codegen_input_protocol!();
|
||||
|
||||
impl AspectIdentifier for InputHandler {
|
||||
impl_aspect_for_input_handler_aspect_id! {}
|
||||
}
|
||||
impl Aspect for InputHandler {
|
||||
impl_aspect_for_input_handler_aspect! {}
|
||||
}
|
||||
impl AspectIdentifier for InputMethod {
|
||||
impl_aspect_for_input_method_aspect_id! {}
|
||||
}
|
||||
impl Aspect for InputMethod {
|
||||
impl_aspect_for_input_method_aspect! {}
|
||||
}
|
||||
impl AspectIdentifier for InputMethodRef {
|
||||
impl_aspect_for_input_method_ref_aspect_id! {}
|
||||
}
|
||||
impl Aspect for InputMethodRef {
|
||||
impl_aspect_for_input_method_ref_aspect! {}
|
||||
}
|
||||
|
||||
pub trait InputDataTrait {
|
||||
fn transform(&mut self, method: &InputMethod, handler: &InputHandler);
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
||||
@@ -47,9 +68,7 @@ impl InputDataTrait for InputDataType {
|
||||
}
|
||||
}
|
||||
|
||||
create_interface!(InputInterface);
|
||||
pub struct InputInterface;
|
||||
impl InterfaceAspect for InputInterface {
|
||||
impl InterfaceAspect for Interface {
|
||||
#[doc = "Create an input method node"]
|
||||
fn create_input_method(
|
||||
_node: Arc<Node>,
|
||||
@@ -122,6 +141,7 @@ pub fn process_input() {
|
||||
.clone()
|
||||
// filter out methods without the handler in their handler order
|
||||
.filter(|a| {
|
||||
let _span = debug_span!("handlder_order lock").entered();
|
||||
a.handler_order
|
||||
.lock()
|
||||
.iter()
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
use super::{
|
||||
create_item_acceptor_flex, register_item_ui_flex, Item, ItemAcceptor, ItemInterface, ItemType,
|
||||
};
|
||||
use super::{create_item_acceptor_flex, register_item_ui_flex, Item, ItemType};
|
||||
use crate::bail;
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::Aspect;
|
||||
use crate::nodes::AspectIdentifier;
|
||||
use crate::{
|
||||
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
|
||||
create_interface,
|
||||
nodes::{
|
||||
drawable::{
|
||||
model::{MaterialWrapper, ModelPart},
|
||||
shaders::UNLIT_SHADER_BYTES,
|
||||
},
|
||||
drawable::{model::ModelPart, shaders::UNLIT_SHADER_BYTES},
|
||||
items::TypeInfo,
|
||||
spatial::{Spatial, Transform},
|
||||
Message, Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::{bail, eyre, Result};
|
||||
use glam::Mat4;
|
||||
use lazy_static::lazy_static;
|
||||
use mint::{ColumnMatrix4, Vector2};
|
||||
@@ -25,20 +22,8 @@ use parking_lot::Mutex;
|
||||
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use std::sync::Arc;
|
||||
use stereokit_rust::{
|
||||
material::{Material, Transparency},
|
||||
shader::Shader,
|
||||
sk::MainThreadToken,
|
||||
system::Renderer,
|
||||
tex::{Tex, TexFormat, TexType},
|
||||
util::Color128,
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
pub struct TexWrapper(pub Tex);
|
||||
unsafe impl Send for TexWrapper {}
|
||||
unsafe impl Sync for TexWrapper {}
|
||||
|
||||
stardust_xr_server_codegen::codegen_item_camera_protocol!();
|
||||
lazy_static! {
|
||||
pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo {
|
||||
@@ -48,6 +33,12 @@ lazy_static! {
|
||||
ui: Default::default(),
|
||||
items: Registry::new(),
|
||||
acceptors: Registry::new(),
|
||||
add_acceptor_aspect: |node| {
|
||||
node.add_aspect(CameraItemAcceptor);
|
||||
},
|
||||
add_ui_aspect: |node| {
|
||||
node.add_aspect(CameraItemUi);
|
||||
},
|
||||
new_acceptor_fn: |node, acceptor, acceptor_field| {
|
||||
let _ = camera_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
|
||||
}
|
||||
@@ -62,30 +53,27 @@ struct FrameInfo {
|
||||
pub struct CameraItem {
|
||||
space: Arc<Spatial>,
|
||||
frame_info: Mutex<FrameInfo>,
|
||||
sk_tex: OnceCell<TexWrapper>,
|
||||
sk_mat: OnceCell<Arc<MaterialWrapper>>,
|
||||
sk_tex: OnceCell<()>,
|
||||
sk_mat: OnceCell<Arc<()>>,
|
||||
applied_to: Registry<ModelPart>,
|
||||
apply_to: Registry<ModelPart>,
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl CameraItem {
|
||||
pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) {
|
||||
Item::add_to(
|
||||
node,
|
||||
&ITEM_TYPE_INFO_CAMERA,
|
||||
ItemType::Camera(CameraItem {
|
||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
frame_info: Mutex::new(FrameInfo {
|
||||
proj_matrix,
|
||||
px_size,
|
||||
}),
|
||||
sk_tex: OnceCell::new(),
|
||||
sk_mat: OnceCell::new(),
|
||||
applied_to: Registry::new(),
|
||||
apply_to: Registry::new(),
|
||||
let item = Arc::new(CameraItem {
|
||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
frame_info: Mutex::new(FrameInfo {
|
||||
proj_matrix,
|
||||
px_size,
|
||||
}),
|
||||
);
|
||||
// <CameraItem as CameraItemAspect>::node_methods(node);
|
||||
sk_tex: OnceCell::new(),
|
||||
sk_mat: OnceCell::new(),
|
||||
applied_to: Registry::new(),
|
||||
apply_to: Registry::new(),
|
||||
});
|
||||
Item::add_to(node, &ITEM_TYPE_INFO_CAMERA, ItemType::Camera(item.clone()));
|
||||
node.add_aspect_raw(item);
|
||||
}
|
||||
|
||||
fn frame_flex(
|
||||
@@ -97,7 +85,7 @@ impl CameraItem {
|
||||
response.wrap_sync(move || {
|
||||
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
|
||||
else {
|
||||
return Err(eyre!("Wrong item type?"));
|
||||
bail!("Wrong item type?");
|
||||
};
|
||||
Ok(serialize(())?.into())
|
||||
});
|
||||
@@ -109,7 +97,7 @@ impl CameraItem {
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let ItemType::Camera(camera) = &node.get_aspect::<Item>().unwrap().specialization else {
|
||||
bail!("Wrong item type?")
|
||||
bail!("Wrong item type?");
|
||||
};
|
||||
let model_part_node =
|
||||
calling_client.get_node("Model part", deserialize(&message.data).unwrap())?;
|
||||
@@ -167,9 +155,31 @@ impl CameraItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for CameraItem {
|
||||
impl_aspect_for_camera_item_aspect_id! {}
|
||||
}
|
||||
impl Aspect for CameraItem {
|
||||
impl_aspect_for_camera_item_aspect! {}
|
||||
}
|
||||
impl CameraItemAspect for CameraItem {}
|
||||
|
||||
impl CameraItemAcceptorAspect for ItemAcceptor {
|
||||
pub struct CameraItemUi;
|
||||
impl AspectIdentifier for CameraItemUi {
|
||||
impl_aspect_for_camera_item_ui_aspect_id! {}
|
||||
}
|
||||
impl Aspect for CameraItemUi {
|
||||
impl_aspect_for_camera_item_ui_aspect! {}
|
||||
}
|
||||
impl CameraItemUiAspect for CameraItemUi {}
|
||||
|
||||
pub struct CameraItemAcceptor;
|
||||
impl AspectIdentifier for CameraItemAcceptor {
|
||||
impl_aspect_for_camera_item_acceptor_aspect_id! {}
|
||||
}
|
||||
impl Aspect for CameraItemAcceptor {
|
||||
impl_aspect_for_camera_item_acceptor_aspect! {}
|
||||
}
|
||||
impl CameraItemAcceptorAspect for CameraItemAcceptor {
|
||||
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
|
||||
super::acceptor_capture_item_flex(node, item)
|
||||
}
|
||||
@@ -184,8 +194,7 @@ pub fn update(token: &MainThreadToken) {
|
||||
}
|
||||
}
|
||||
|
||||
create_interface!(ItemInterface);
|
||||
impl InterfaceAspect for ItemInterface {
|
||||
impl InterfaceAspect for Interface {
|
||||
#[doc = "Create a camera item at a specific location"]
|
||||
fn create_camera_item(
|
||||
_node: Arc<Node>,
|
||||
@@ -206,19 +215,21 @@ impl InterfaceAspect for ItemInterface {
|
||||
}
|
||||
|
||||
#[doc = "Register this client to manage camera items and create default 3D UI for them."]
|
||||
fn register_camera_item_ui(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||
fn register_camera_item_ui(node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||
node.add_aspect(CameraItemUi);
|
||||
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_CAMERA)
|
||||
}
|
||||
|
||||
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/camera/acceptor/<name>`."]
|
||||
fn create_camera_item_acceptor(
|
||||
_node: Arc<Node>,
|
||||
node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
field: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
node.add_aspect(CameraItemAcceptor);
|
||||
create_item_acceptor_flex(
|
||||
calling_client,
|
||||
id,
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
pub mod camera;
|
||||
// TODO: reimplement with bevy
|
||||
// pub mod camera;
|
||||
pub mod panel;
|
||||
|
||||
use self::camera::CameraItem;
|
||||
// use self::camera::CameraItem;
|
||||
use self::panel::PanelItemTrait;
|
||||
use super::alias::AliasList;
|
||||
use super::fields::{Field, FIELD_ALIAS_INFO};
|
||||
use super::spatial::Spatial;
|
||||
use super::{Alias, Aspect, Node};
|
||||
use super::{Alias, Aspect, AspectIdentifier, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::core::error::Result;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::ensure;
|
||||
use crate::nodes::alias::AliasInfo;
|
||||
use crate::nodes::spatial::Transform;
|
||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use parking_lot::Mutex;
|
||||
use std::hash::Hash;
|
||||
use std::sync::{Arc, Weak};
|
||||
@@ -47,6 +49,8 @@ pub struct TypeInfo {
|
||||
pub ui: Mutex<Weak<ItemUI>>,
|
||||
pub items: Registry<Item>,
|
||||
pub acceptors: Registry<ItemAcceptor>,
|
||||
pub add_ui_aspect: fn(node: &Node),
|
||||
pub add_acceptor_aspect: fn(node: &Node),
|
||||
pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc<Node>, acceptor_field: &Arc<Node>),
|
||||
}
|
||||
impl Hash for TypeInfo {
|
||||
@@ -81,7 +85,6 @@ impl Item {
|
||||
};
|
||||
let item = type_info.items.add(item);
|
||||
|
||||
<Item as ItemAspect>::add_node_members(node);
|
||||
if let Some(ui) = type_info.ui.lock().upgrade() {
|
||||
ui.handle_create_item(&item);
|
||||
}
|
||||
@@ -108,8 +111,11 @@ impl Item {
|
||||
)
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Item {
|
||||
impl_aspect_for_item_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Item {
|
||||
const NAME: &'static str = "Item";
|
||||
impl_aspect_for_item_aspect! {}
|
||||
}
|
||||
impl ItemAspect for Item {
|
||||
fn release(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
@@ -129,19 +135,19 @@ impl Drop for Item {
|
||||
}
|
||||
|
||||
pub enum ItemType {
|
||||
Camera(CameraItem),
|
||||
// Camera(CameraItem),
|
||||
Panel(Arc<dyn PanelItemTrait>),
|
||||
}
|
||||
impl ItemType {
|
||||
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||
match self {
|
||||
ItemType::Camera(c) => c.send_ui_item_created(node, item),
|
||||
// ItemType::Camera(c) => c.send_ui_item_created(node, item),
|
||||
ItemType::Panel(p) => p.send_ui_item_created(node, item),
|
||||
}
|
||||
}
|
||||
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||
match self {
|
||||
ItemType::Camera(c) => c.send_acceptor_item_created(node, item),
|
||||
// ItemType::Camera(c) => c.send_acceptor_item_created(node, item),
|
||||
ItemType::Panel(p) => p.send_acceptor_item_created(node, item),
|
||||
}
|
||||
}
|
||||
@@ -284,8 +290,11 @@ impl ItemUI {
|
||||
.remove_aspect(acceptor.field.as_ref());
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for ItemUI {
|
||||
impl_aspect_for_item_ui_aspect_id! {}
|
||||
}
|
||||
impl Aspect for ItemUI {
|
||||
const NAME: &'static str = "Item";
|
||||
impl_aspect_for_item_ui_aspect! {}
|
||||
}
|
||||
impl Drop for ItemUI {
|
||||
fn drop(&mut self) {
|
||||
@@ -342,8 +351,11 @@ impl ItemAcceptor {
|
||||
let _ = item_acceptor_client::release_item(&node, alias.id);
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for ItemAcceptor {
|
||||
impl_aspect_for_item_acceptor_aspect_id! {}
|
||||
}
|
||||
impl Aspect for ItemAcceptor {
|
||||
const NAME: &'static str = "ItemAcceptor";
|
||||
impl_aspect_for_item_acceptor_aspect! {}
|
||||
}
|
||||
impl ItemAcceptorAspect for ItemAcceptor {}
|
||||
impl Drop for ItemAcceptor {
|
||||
@@ -364,6 +376,7 @@ pub fn register_item_ui_flex(
|
||||
) -> Result<()> {
|
||||
let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?;
|
||||
ItemUI::add_to(&ui, type_info)?;
|
||||
(type_info.add_ui_aspect)(&ui);
|
||||
Ok(())
|
||||
}
|
||||
fn create_item_acceptor_flex(
|
||||
@@ -381,6 +394,7 @@ fn create_item_acceptor_flex(
|
||||
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(space.clone()), transform, false);
|
||||
ItemAcceptor::add_to(&node, type_info, field);
|
||||
(type_info.add_acceptor_aspect)(&node);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -391,6 +405,3 @@ fn acceptor_capture_item_flex(node: Arc<Node>, item: Arc<Node>) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ItemInterface;
|
||||
// create_interface!(ItemInterface);
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use super::{create_item_acceptor_flex, register_item_ui_flex, ItemAcceptor, ItemInterface};
|
||||
// use super::camera::CameraItemAcceptor;
|
||||
use super::{create_item_acceptor_flex, register_item_ui_flex};
|
||||
use crate::bail;
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::{Aspect, AspectIdentifier};
|
||||
use crate::{
|
||||
core::{
|
||||
client::{get_env, state, Client, INTERNAL_CLIENT},
|
||||
registry::Registry,
|
||||
},
|
||||
create_interface,
|
||||
nodes::{
|
||||
drawable::model::ModelPart,
|
||||
items::{Item, ItemType, TypeInfo},
|
||||
@@ -14,10 +17,11 @@ use crate::{
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Mat4;
|
||||
use lazy_static::lazy_static;
|
||||
use mint::Vector2;
|
||||
use parking_lot::Mutex;
|
||||
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
|
||||
use std::sync::{Arc, Weak};
|
||||
use tracing::{debug, info};
|
||||
|
||||
@@ -32,6 +36,7 @@ impl Default for Geometry {
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
|
||||
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
|
||||
type_name: "panel",
|
||||
alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(),
|
||||
@@ -39,6 +44,12 @@ lazy_static! {
|
||||
ui: Default::default(),
|
||||
items: Registry::new(),
|
||||
acceptors: Registry::new(),
|
||||
add_acceptor_aspect: |node| {
|
||||
node.add_aspect(PanelItemUi);
|
||||
},
|
||||
add_ui_aspect: |node| {
|
||||
node.add_aspect(PanelItemAcceptor);
|
||||
},
|
||||
new_acceptor_fn: |node, acceptor, acceptor_field| {
|
||||
let _ = panel_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
|
||||
}
|
||||
@@ -65,7 +76,7 @@ pub trait Backend: Send + Sync + 'static {
|
||||
scroll_steps: Option<Vector2<f32>>,
|
||||
);
|
||||
|
||||
fn keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec<i32>);
|
||||
fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool);
|
||||
|
||||
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>);
|
||||
fn touch_move(&self, id: u32, position: Vector2<f32>);
|
||||
@@ -115,7 +126,7 @@ impl<B: Backend> PanelItem<B> {
|
||||
&ITEM_TYPE_INFO_PANEL,
|
||||
ItemType::Panel(generic_panel_item),
|
||||
);
|
||||
<Self as PanelItemAspect>::add_node_members(&node);
|
||||
node.add_aspect_raw(panel_item.clone());
|
||||
|
||||
(node, panel_item)
|
||||
}
|
||||
@@ -197,9 +208,12 @@ impl<B: Backend> PanelItem<B> {
|
||||
panel_item_client::destroy_child(&node, id);
|
||||
}
|
||||
}
|
||||
|
||||
// make these stupid vectors u32 in the protocol somehow!!!!!!!1
|
||||
|
||||
impl<B: Backend> AspectIdentifier for PanelItem<B> {
|
||||
impl_aspect_for_panel_item_aspect_id! {}
|
||||
}
|
||||
impl<B: Backend> Aspect for PanelItem<B> {
|
||||
impl_aspect_for_panel_item_aspect! {}
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl<B: Backend> PanelItemAspect for PanelItem<B> {
|
||||
#[doc = "Apply the cursor as a material to a model."]
|
||||
@@ -341,19 +355,20 @@ impl<B: Backend> PanelItemAspect for PanelItem<B> {
|
||||
}
|
||||
|
||||
#[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."]
|
||||
fn keyboard_keys(
|
||||
fn keyboard_key(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
surface: SurfaceId,
|
||||
keymap_id: u64,
|
||||
keys: Vec<i32>,
|
||||
key: u32,
|
||||
pressed: bool,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item
|
||||
.backend()
|
||||
.keyboard_keys(&surface, keymap_id, keys);
|
||||
.keyboard_key(&surface, keymap_id, key, pressed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -405,7 +420,23 @@ impl<B: Backend> PanelItemAspect for PanelItem<B> {
|
||||
}
|
||||
}
|
||||
|
||||
impl PanelItemAcceptorAspect for ItemAcceptor {
|
||||
pub struct PanelItemUi;
|
||||
impl AspectIdentifier for PanelItemUi {
|
||||
impl_aspect_for_panel_item_ui_aspect_id! {}
|
||||
}
|
||||
impl Aspect for PanelItemUi {
|
||||
impl_aspect_for_panel_item_ui_aspect! {}
|
||||
}
|
||||
impl PanelItemUiAspect for PanelItemUi {}
|
||||
|
||||
pub struct PanelItemAcceptor;
|
||||
impl AspectIdentifier for PanelItemAcceptor {
|
||||
impl_aspect_for_panel_item_acceptor_aspect_id! {}
|
||||
}
|
||||
impl Aspect for PanelItemAcceptor {
|
||||
impl_aspect_for_panel_item_acceptor_aspect! {}
|
||||
}
|
||||
impl PanelItemAcceptorAspect for PanelItemAcceptor {
|
||||
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
|
||||
super::acceptor_capture_item_flex(node, item)
|
||||
}
|
||||
@@ -435,22 +466,23 @@ impl<B: Backend> Drop for PanelItem<B> {
|
||||
}
|
||||
}
|
||||
|
||||
create_interface!(ItemInterface);
|
||||
impl InterfaceAspect for ItemInterface {
|
||||
impl InterfaceAspect for Interface {
|
||||
#[doc = "Register this client to manage the items of a certain type and create default 3D UI for them."]
|
||||
fn register_panel_item_ui(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||
fn register_panel_item_ui(node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||
// node.add_aspect(CameraItemAcceptor);
|
||||
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_PANEL)
|
||||
}
|
||||
|
||||
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/<item_type>/acceptor/<name>`."]
|
||||
fn create_panel_item_acceptor(
|
||||
_node: Arc<Node>,
|
||||
node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
field: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
node.add_aspect(PanelItemAcceptor);
|
||||
create_item_acceptor_flex(
|
||||
calling_client,
|
||||
id,
|
||||
@@ -460,4 +492,36 @@ impl InterfaceAspect for ItemInterface {
|
||||
field,
|
||||
)
|
||||
}
|
||||
|
||||
async fn register_keymap(
|
||||
_node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
keymap: String,
|
||||
) -> Result<u64> {
|
||||
let mut keymaps = KEYMAPS.lock();
|
||||
if let Some(found_keymap_id) = keymaps
|
||||
.iter()
|
||||
.filter(|(_k, v)| *v == &keymap)
|
||||
.map(|(k, _v)| k)
|
||||
.last()
|
||||
{
|
||||
return Ok(found_keymap_id.data().as_ffi());
|
||||
}
|
||||
|
||||
let key = keymaps.insert(keymap);
|
||||
Ok(key.data().as_ffi())
|
||||
}
|
||||
|
||||
async fn get_keymap(
|
||||
_node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
keymap_id: u64,
|
||||
) -> Result<String> {
|
||||
let keymaps = KEYMAPS.lock();
|
||||
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else {
|
||||
bail!("Could not find keymap. Try registering it");
|
||||
};
|
||||
|
||||
Ok(keymap.clone())
|
||||
}
|
||||
}
|
||||
|
||||
192
src/nodes/mod.rs
192
src/nodes/mod.rs
@@ -1,6 +1,5 @@
|
||||
pub mod alias;
|
||||
pub mod audio;
|
||||
pub mod data;
|
||||
pub mod drawable;
|
||||
pub mod fields;
|
||||
pub mod input;
|
||||
@@ -10,10 +9,10 @@ pub mod spatial;
|
||||
|
||||
use self::alias::Alias;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::error::{Result, ServerError};
|
||||
use crate::core::queued_mutex::QueuedMutex;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::core::scenegraph::MethodResponseSender;
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use parking_lot::Mutex;
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
@@ -21,6 +20,7 @@ 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;
|
||||
@@ -46,11 +46,29 @@ impl AsRef<[u8]> for Message {
|
||||
}
|
||||
}
|
||||
|
||||
pub type Signal = fn(Arc<Node>, Arc<Client>, Message) -> Result<()>;
|
||||
pub type Method = fn(Arc<Node>, Arc<Client>, Message, MethodResponseSender);
|
||||
|
||||
stardust_xr_server_codegen::codegen_node_protocol!();
|
||||
|
||||
pub struct Owned;
|
||||
impl AspectIdentifier for Owned {
|
||||
impl_aspect_for_owned_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Owned {
|
||||
impl_aspect_for_owned_aspect! {}
|
||||
}
|
||||
impl OwnedAspect for Owned {
|
||||
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
|
||||
node.set_enabled(enabled);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
if node.destroyable {
|
||||
node.destroy();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OwnedNode(pub Arc<Node>);
|
||||
impl Drop for OwnedNode {
|
||||
fn drop(&mut self) {
|
||||
@@ -64,8 +82,6 @@ pub struct Node {
|
||||
client: Weak<Client>,
|
||||
message_sender_handle: Option<MessageSenderHandle>,
|
||||
|
||||
local_signals: Mutex<FxHashMap<u64, Signal>>,
|
||||
local_methods: Mutex<FxHashMap<u64, Method>>,
|
||||
aliases: Registry<Alias>,
|
||||
aspects: Aspects,
|
||||
destroyable: bool,
|
||||
@@ -87,41 +103,47 @@ impl Node {
|
||||
client: Arc::downgrade(client),
|
||||
message_sender_handle: client.message_sender_handle.clone(),
|
||||
id,
|
||||
local_signals: Default::default(),
|
||||
local_methods: Default::default(),
|
||||
aliases: Default::default(),
|
||||
aspects: Default::default(),
|
||||
destroyable,
|
||||
};
|
||||
<Node as OwnedAspect>::add_node_members(&node);
|
||||
node.aspects.add(Owned);
|
||||
node
|
||||
}
|
||||
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
|
||||
Ok(self
|
||||
.get_client()
|
||||
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
|
||||
.ok_or(ServerError::NoClient)?
|
||||
.scenegraph
|
||||
.add_node(self))
|
||||
}
|
||||
pub fn add_to_scenegraph_owned(self) -> Result<OwnedNode> {
|
||||
Ok(OwnedNode(
|
||||
self.get_client()
|
||||
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
|
||||
.ok_or(ServerError::NoClient)?
|
||||
.scenegraph
|
||||
.add_node(self),
|
||||
))
|
||||
}
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled.load(Ordering::Relaxed)
|
||||
&& if let Ok(spatial) = self.get_aspect::<Spatial>() {
|
||||
spatial
|
||||
.global_transform()
|
||||
.to_scale_rotation_translation()
|
||||
.0
|
||||
.length_squared() > 0.0
|
||||
} else {
|
||||
true
|
||||
}
|
||||
let bool = {
|
||||
let _span = debug_span!("load atomic bool").entered();
|
||||
self.enabled.load(Ordering::Relaxed)
|
||||
};
|
||||
bool && if let Ok(spatial) = {
|
||||
let _span = debug_span!("get spatial aspect").entered();
|
||||
self.get_aspect::<Spatial>()
|
||||
} {
|
||||
let _span = debug_span!("check if scale is zero").entered();
|
||||
spatial
|
||||
.global_transform()
|
||||
.to_scale_rotation_translation()
|
||||
.0
|
||||
.length_squared()
|
||||
> 0.0
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
pub fn set_enabled(&self, enabled: bool) {
|
||||
self.enabled.store(enabled, Ordering::Relaxed)
|
||||
@@ -146,60 +168,58 @@ impl Node {
|
||||
// Ok(serialize(pid)?.into())
|
||||
// }
|
||||
|
||||
pub fn add_local_signal(&self, id: u64, signal: Signal) {
|
||||
self.local_signals.lock().insert(id, signal);
|
||||
}
|
||||
pub fn add_local_method(&self, id: u64, method: Method) {
|
||||
self.local_methods.lock().insert(id, method);
|
||||
}
|
||||
|
||||
pub fn add_aspect<A: Aspect>(&self, aspect: A) -> Arc<A> {
|
||||
pub fn add_aspect<A: AspectIdentifier>(&self, aspect: A) -> Arc<A> {
|
||||
self.aspects.add(aspect)
|
||||
}
|
||||
pub fn add_aspect_raw<A: Aspect>(&self, aspect: Arc<A>) {
|
||||
pub fn add_aspect_raw<A: AspectIdentifier>(&self, aspect: Arc<A>) {
|
||||
self.aspects.add_raw(aspect)
|
||||
}
|
||||
pub fn get_aspect<A: Aspect>(&self) -> Result<Arc<A>> {
|
||||
pub fn get_aspect<A: AspectIdentifier>(&self) -> Result<Arc<A>> {
|
||||
self.aspects.get()
|
||||
}
|
||||
|
||||
pub fn send_local_signal(
|
||||
self: Arc<Self>,
|
||||
calling_client: Arc<Client>,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
message: Message,
|
||||
) -> Result<(), ScenegraphError> {
|
||||
if let Ok(alias) = self.get_aspect::<Alias>() {
|
||||
if !alias.info.server_signals.iter().any(|e| *e == method) {
|
||||
return Err(ScenegraphError::SignalNotFound);
|
||||
return Err(ScenegraphError::MemberNotFound);
|
||||
}
|
||||
alias
|
||||
.original
|
||||
.upgrade()
|
||||
.ok_or(ScenegraphError::BrokenAlias)?
|
||||
.send_local_signal(calling_client, method, message)
|
||||
.send_local_signal(calling_client, aspect_id, method, message)
|
||||
} else {
|
||||
let signal = self
|
||||
.local_signals
|
||||
let aspect = self
|
||||
.aspects
|
||||
.0
|
||||
.lock()
|
||||
.get(&method)
|
||||
.cloned()
|
||||
.ok_or(ScenegraphError::SignalNotFound)?;
|
||||
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
|
||||
error: error.to_string(),
|
||||
})
|
||||
.get(&aspect_id)
|
||||
.ok_or(ScenegraphError::AspectNotFound)?
|
||||
.clone();
|
||||
aspect
|
||||
.run_signal(calling_client, self.clone(), method, message)
|
||||
.map_err(|error| ScenegraphError::MemberError {
|
||||
error: error.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn execute_local_method(
|
||||
self: Arc<Self>,
|
||||
calling_client: Arc<Client>,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
message: Message,
|
||||
response: MethodResponseSender,
|
||||
) {
|
||||
if let Ok(alias) = self.get_aspect::<Alias>() {
|
||||
if !alias.info.server_methods.iter().any(|e| *e == method) {
|
||||
response.send(Err(ScenegraphError::MethodNotFound));
|
||||
response.send(Err(ScenegraphError::MemberNotFound));
|
||||
return;
|
||||
}
|
||||
let Some(alias) = alias.original.upgrade() else {
|
||||
@@ -208,6 +228,7 @@ impl Node {
|
||||
};
|
||||
alias.execute_local_method(
|
||||
calling_client,
|
||||
aspect_id,
|
||||
method,
|
||||
Message {
|
||||
data: message.data.clone(),
|
||||
@@ -216,14 +237,19 @@ impl Node {
|
||||
response,
|
||||
)
|
||||
} else {
|
||||
let Some(method) = self.local_methods.lock().get(&method).cloned() else {
|
||||
response.send(Err(ScenegraphError::MethodNotFound));
|
||||
let Some(aspect) = self.aspects.0.lock().get(&aspect_id).cloned() else {
|
||||
response.send(Err(ScenegraphError::AspectNotFound));
|
||||
return;
|
||||
};
|
||||
method(self, calling_client, message, response);
|
||||
aspect.run_method(calling_client, self.clone(), method, message, response);
|
||||
}
|
||||
}
|
||||
pub fn send_remote_signal(&self, method: u64, message: impl Into<Message>) -> Result<()> {
|
||||
pub fn send_remote_signal(
|
||||
&self,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
message: impl Into<Message>,
|
||||
) -> Result<()> {
|
||||
let message = message.into();
|
||||
self.aliases
|
||||
.get_valid_contents()
|
||||
@@ -233,6 +259,7 @@ impl Node {
|
||||
.for_each(|node| {
|
||||
// Beware! file descriptors will not be sent to aliases!!!
|
||||
let _ = node.send_remote_signal(
|
||||
aspect_id,
|
||||
method,
|
||||
Message {
|
||||
data: message.data.clone(),
|
||||
@@ -241,12 +268,13 @@ impl Node {
|
||||
);
|
||||
});
|
||||
if let Some(handle) = self.message_sender_handle.as_ref() {
|
||||
handle.signal(self.id, method, &message.data, message.fds)?;
|
||||
handle.signal(self.id, aspect_id, method, &message.data, message.fds)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
|
||||
&self,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
input: S,
|
||||
fds: Vec<OwnedFd>,
|
||||
@@ -254,13 +282,13 @@ impl Node {
|
||||
let message_sender_handle = self
|
||||
.message_sender_handle
|
||||
.as_ref()
|
||||
.ok_or(eyre!("Messenger does not exist for this node"))?;
|
||||
.ok_or(ServerError::NoMessenger)?;
|
||||
|
||||
let serialized = serialize(input)?;
|
||||
let result = message_sender_handle
|
||||
.method(self.id, method, &serialized, fds)?
|
||||
.method(self.id, aspect_id, method, &serialized, fds)?
|
||||
.await
|
||||
.map_err(|e| eyre!(e))?;
|
||||
.map_err(ServerError::RemoteMethodError)?;
|
||||
|
||||
let (message, fds) = result.into_components();
|
||||
let deserialized: D = deserialize(&message)?;
|
||||
@@ -271,57 +299,57 @@ impl Debug for Node {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Node")
|
||||
.field("id", &self.id)
|
||||
.field("local_signals", &self.local_signals.lock().keys())
|
||||
.field("local_methods", &self.local_methods.lock().keys())
|
||||
.field("destroyable", &self.destroyable)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl OwnedAspect for Node {
|
||||
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
|
||||
node.set_enabled(enabled);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
if node.destroyable {
|
||||
node.destroy();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for Node {
|
||||
fn drop(&mut self) {
|
||||
// Debug breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AspectIdentifier: Aspect {
|
||||
const ID: u64;
|
||||
}
|
||||
pub trait Aspect: Any + Send + Sync + 'static {
|
||||
const NAME: &'static str;
|
||||
fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static>;
|
||||
fn run_signal(
|
||||
&self,
|
||||
calling_client: Arc<Client>,
|
||||
node: Arc<Node>,
|
||||
signal: u64,
|
||||
message: Message,
|
||||
) -> Result<(), stardust_xr::scenegraph::ScenegraphError>;
|
||||
fn run_method(
|
||||
&self,
|
||||
calling_client: Arc<Client>,
|
||||
node: Arc<Node>,
|
||||
method: u64,
|
||||
message: Message,
|
||||
response: MethodResponseSender,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Aspects(Mutex<FxHashMap<TypeId, Arc<dyn Any + Send + Sync + 'static>>>);
|
||||
|
||||
struct Aspects(QueuedMutex<FxHashMap<u64, Arc<dyn Aspect>>>);
|
||||
impl Aspects {
|
||||
fn add<A: Aspect>(&self, t: A) -> Arc<A> {
|
||||
fn add<A: AspectIdentifier>(&self, t: A) -> Arc<A> {
|
||||
let aspect = Arc::new(t);
|
||||
self.add_raw(aspect.clone());
|
||||
aspect
|
||||
}
|
||||
fn add_raw<A: Aspect>(&self, aspect: Arc<A>) {
|
||||
self.0.lock().insert(Self::type_key::<A>(), aspect);
|
||||
fn add_raw<A: AspectIdentifier>(&self, aspect: Arc<A>) {
|
||||
self.0.lock().insert(A::ID, aspect);
|
||||
}
|
||||
fn get<A: Aspect>(&self) -> Result<Arc<A>> {
|
||||
fn get<A: Aspect + AspectIdentifier>(&self) -> Result<Arc<A>> {
|
||||
self.0
|
||||
.lock()
|
||||
.get(&Self::type_key::<A>())
|
||||
.and_then(|a| Arc::downcast(a.clone()).ok())
|
||||
.ok_or(eyre!("Couldn't get aspect {}", A::NAME.to_lowercase()))
|
||||
}
|
||||
|
||||
fn type_key<A: 'static>() -> TypeId {
|
||||
TypeId::of::<A>()
|
||||
.read_now()
|
||||
.get(&A::ID)
|
||||
.cloned()
|
||||
.map(|a| a.as_any())
|
||||
.and_then(|a| Arc::downcast(a).ok())
|
||||
.ok_or(ServerError::NoAspect(TypeId::of::<A>()))
|
||||
}
|
||||
}
|
||||
impl Drop for Aspects {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use super::spatial::Spatial;
|
||||
use super::Node;
|
||||
use super::{Aspect, AspectIdentifier, Node};
|
||||
use crate::bail;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::client_state::ClientStateParsed;
|
||||
use crate::core::error::Result;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||
use crate::session::connection_env;
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use glam::Mat4;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -23,14 +24,14 @@ pub struct Root {
|
||||
impl Root {
|
||||
pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> {
|
||||
let node = Node::from_id(client, 0, false);
|
||||
<Self as RootAspect>::add_node_members(&node);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
let _ = Spatial::add_to(&node, None, transform, false);
|
||||
|
||||
Ok(ROOT_REGISTRY.add(Root {
|
||||
node,
|
||||
let root_aspect = node.add_aspect(Root {
|
||||
node: node.clone(),
|
||||
connect_instant: Instant::now(),
|
||||
}))
|
||||
});
|
||||
ROOT_REGISTRY.add_raw(&root_aspect);
|
||||
Ok(root_aspect)
|
||||
}
|
||||
|
||||
pub fn send_frame_events(delta: f64) {
|
||||
@@ -54,6 +55,12 @@ impl Root {
|
||||
Ok(root_client::save_state(&self.node).await?.0)
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Root {
|
||||
impl_aspect_for_root_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Root {
|
||||
impl_aspect_for_root_aspect! {}
|
||||
}
|
||||
impl RootAspect for Root {
|
||||
async fn get_state(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<ClientState> {
|
||||
let Some(state) = calling_client.state.get() else {
|
||||
@@ -92,7 +99,7 @@ impl RootAspect for Root {
|
||||
}
|
||||
|
||||
#[doc = "Cleanly disconnect from the server"]
|
||||
fn disconnect(_node: Arc<Node>, calling_client: Arc<Client>) -> color_eyre::eyre::Result<()> {
|
||||
fn disconnect(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||
calling_client.disconnect(Ok(()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,21 +3,22 @@ pub mod zone;
|
||||
use self::zone::Zone;
|
||||
use super::alias::Alias;
|
||||
use super::fields::{Field, FieldTrait};
|
||||
use super::Aspect;
|
||||
use super::{Aspect, AspectIdentifier};
|
||||
use crate::bail;
|
||||
use crate::bevy_plugin::StardustAabb3dExt;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::error::Result;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::create_interface;
|
||||
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
|
||||
use color_eyre::eyre::{eyre, OptionExt, Result};
|
||||
use glam::{vec3a, Mat4, Quat, Vec3};
|
||||
use bevy::math::bounding::{Aabb3d, BoundingVolume};
|
||||
use color_eyre::eyre::OptionExt;
|
||||
use glam::{vec3a, Mat4, Quat, Vec3, Vec3A};
|
||||
use mint::Vector3;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use stereokit_rust::maths::Bounds;
|
||||
|
||||
stardust_xr_server_codegen::codegen_spatial_protocol!();
|
||||
impl Transform {
|
||||
@@ -38,6 +39,12 @@ impl Transform {
|
||||
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Zone {
|
||||
impl_aspect_for_zone_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Zone {
|
||||
impl_aspect_for_zone_aspect! {}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref EXPORTED_SPATIALS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default());
|
||||
@@ -52,7 +59,7 @@ pub struct Spatial {
|
||||
transform: Mutex<Mat4>,
|
||||
zone: Mutex<Weak<Zone>>,
|
||||
children: Registry<Spatial>,
|
||||
pub bounding_box_calc: OnceCell<fn(&Node) -> Bounds>,
|
||||
pub bounding_box_calc: Mutex<Aabb3d>,
|
||||
}
|
||||
|
||||
impl Spatial {
|
||||
@@ -64,7 +71,7 @@ impl Spatial {
|
||||
transform: Mutex::new(transform),
|
||||
zone: Mutex::new(Weak::new()),
|
||||
children: Registry::new(),
|
||||
bounding_box_calc: OnceCell::default(),
|
||||
bounding_box_calc: Mutex::new(Aabb3d::new(Vec3A::ZERO, Vec3A::ZERO)),
|
||||
})
|
||||
}
|
||||
pub fn add_to(
|
||||
@@ -74,16 +81,14 @@ impl Spatial {
|
||||
zoneable: bool,
|
||||
) -> Arc<Spatial> {
|
||||
let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform);
|
||||
<Spatial as SpatialAspect>::add_node_members(node);
|
||||
if zoneable {
|
||||
ZONEABLE_REGISTRY.add_raw(&spatial);
|
||||
}
|
||||
if let Some(parent) = parent {
|
||||
parent.children.add_raw(&spatial);
|
||||
}
|
||||
<Spatial as SpatialRefAspect>::add_node_members(node);
|
||||
<Spatial as SpatialAspect>::add_node_members(node);
|
||||
node.add_aspect_raw(spatial.clone());
|
||||
node.add_aspect(SpatialRef);
|
||||
spatial
|
||||
}
|
||||
|
||||
@@ -98,17 +103,12 @@ impl Spatial {
|
||||
}
|
||||
|
||||
// the output bounds are probably way bigger than they need to be
|
||||
pub fn get_bounding_box(&self) -> Bounds {
|
||||
let Some(node) = self.node() else {
|
||||
return Bounds::default();
|
||||
};
|
||||
let mut bounds = self
|
||||
.bounding_box_calc
|
||||
.get()
|
||||
.map(|b| (b)(&node))
|
||||
.unwrap_or_default();
|
||||
pub fn get_bounding_box(&self) -> Aabb3d {
|
||||
let mut bounds = *self.bounding_box_calc.lock();
|
||||
for child in self.children.get_valid_contents() {
|
||||
bounds.grown_box(child.get_bounding_box(), child.local_transform());
|
||||
let b = child.get_bounding_box();
|
||||
let t = child.local_transform();
|
||||
bounds = bounds.grown_box(&b, Some(t));
|
||||
}
|
||||
bounds
|
||||
}
|
||||
@@ -205,7 +205,7 @@ impl Spatial {
|
||||
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
||||
.unwrap_or(false);
|
||||
if is_ancestor {
|
||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
||||
bail!("Setting spatial parent would cause a loop");
|
||||
}
|
||||
self.set_parent(parent);
|
||||
|
||||
@@ -220,7 +220,7 @@ impl Spatial {
|
||||
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
||||
.unwrap_or(false);
|
||||
if is_ancestor {
|
||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
||||
bail!("Setting spatial parent would cause a loop");
|
||||
}
|
||||
|
||||
self.set_local_transform(Spatial::space_to_space_matrix(
|
||||
@@ -241,8 +241,11 @@ impl Spatial {
|
||||
.unwrap_or(f32::MAX)
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Spatial {
|
||||
impl_aspect_for_spatial_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Spatial {
|
||||
const NAME: &'static str = "Spatial";
|
||||
impl_aspect_for_spatial_aspect! {}
|
||||
}
|
||||
impl SpatialRefAspect for Spatial {
|
||||
async fn get_local_bounding_box(
|
||||
@@ -253,8 +256,8 @@ impl SpatialRefAspect for Spatial {
|
||||
let bounds = this_spatial.get_bounding_box();
|
||||
|
||||
Ok(BoundingBox {
|
||||
center: Vec3::from(bounds.center).into(),
|
||||
size: Vec3::from(bounds.dimensions).into(),
|
||||
center: Vec3::from(bounds.center()).into(),
|
||||
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -267,18 +270,18 @@ impl SpatialRefAspect for Spatial {
|
||||
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
||||
.transform_point3([0.0; 3].into());
|
||||
let mut bounds = Bounds {
|
||||
center: center.into(),
|
||||
dimensions: [0.0; 3].into(),
|
||||
};
|
||||
bounds.grown_box(
|
||||
this_spatial.get_bounding_box(),
|
||||
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)),
|
||||
let mut bounds = Aabb3d::new(center, Vec3A::ZERO);
|
||||
bounds = bounds.grown_box(
|
||||
&this_spatial.get_bounding_box(),
|
||||
Some(Spatial::space_to_space_matrix(
|
||||
Some(&this_spatial),
|
||||
Some(&relative_spatial),
|
||||
)),
|
||||
);
|
||||
|
||||
Ok(BoundingBox {
|
||||
center: Vec3::from(bounds.center).into(),
|
||||
size: Vec3::from(bounds.dimensions).into(),
|
||||
center: Vec3::from(bounds.center()).into(),
|
||||
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -389,6 +392,73 @@ impl Drop for Spatial {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpatialRef;
|
||||
impl AspectIdentifier for SpatialRef {
|
||||
impl_aspect_for_spatial_ref_aspect_id! {}
|
||||
}
|
||||
impl Aspect for SpatialRef {
|
||||
impl_aspect_for_spatial_ref_aspect! {}
|
||||
}
|
||||
impl SpatialRefAspect for SpatialRef {
|
||||
async fn get_local_bounding_box(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
) -> Result<BoundingBox> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
let bounds = this_spatial.get_bounding_box();
|
||||
|
||||
Ok(BoundingBox {
|
||||
center: Vec3::from(bounds.center()).into(),
|
||||
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_relative_bounding_box(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
relative_to: Arc<Node>,
|
||||
) -> Result<BoundingBox> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
||||
.transform_point3([0.0; 3].into());
|
||||
let mut bounds = Aabb3d::new(center, [0.0; 3]);
|
||||
bounds = bounds.grown_box(
|
||||
&this_spatial.get_bounding_box(),
|
||||
Some(Spatial::space_to_space_matrix(
|
||||
Some(&this_spatial),
|
||||
Some(&relative_spatial),
|
||||
)),
|
||||
);
|
||||
|
||||
Ok(BoundingBox {
|
||||
center: Vec3::from(bounds.center()).into(),
|
||||
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_transform(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
relative_to: Arc<Node>,
|
||||
) -> Result<Transform> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||
|
||||
let (scale, rotation, position) = Spatial::space_to_space_matrix(
|
||||
Some(this_spatial.as_ref()),
|
||||
Some(relative_spatial.as_ref()),
|
||||
)
|
||||
.to_scale_rotation_translation();
|
||||
|
||||
Ok(Transform {
|
||||
translation: Some(position.into()),
|
||||
rotation: Some(rotation.into()),
|
||||
scale: Some(scale.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
|
||||
let position = position
|
||||
.then_some(transform.translation)
|
||||
@@ -406,8 +476,7 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
|
||||
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
||||
}
|
||||
|
||||
pub struct SpatialInterface;
|
||||
impl InterfaceAspect for SpatialInterface {
|
||||
impl InterfaceAspect for Interface {
|
||||
fn create_spatial(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
@@ -445,7 +514,7 @@ impl InterfaceAspect for SpatialInterface {
|
||||
calling_client: Arc<Client>,
|
||||
uid: u64,
|
||||
) -> Result<Arc<Node>> {
|
||||
EXPORTED_SPATIALS
|
||||
Ok(EXPORTED_SPATIALS
|
||||
.lock()
|
||||
.get(&uid)
|
||||
.map(|s| {
|
||||
@@ -457,8 +526,6 @@ impl InterfaceAspect for SpatialInterface {
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.ok_or_eyre("Couldn't find spatial with that ID")
|
||||
.ok_or_eyre("Couldn't find spatial with that ID")?)
|
||||
}
|
||||
}
|
||||
|
||||
create_interface!(SpatialInterface);
|
||||
|
||||
@@ -3,14 +3,13 @@ use super::{
|
||||
ZONEABLE_REGISTRY,
|
||||
};
|
||||
use crate::{
|
||||
core::{client::Client, registry::Registry},
|
||||
core::{client::Client, error::Result, registry::Registry},
|
||||
nodes::{
|
||||
alias::{get_original, Alias, AliasList},
|
||||
fields::{Field, FieldTrait},
|
||||
Aspect, Node,
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::vec3a;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
@@ -73,7 +72,6 @@ impl Zone {
|
||||
intersecting: AliasList::default(),
|
||||
captured: AliasList::default(),
|
||||
});
|
||||
<Zone as ZoneAspect>::add_node_members(node);
|
||||
node.add_aspect_raw(zone.clone());
|
||||
zone
|
||||
}
|
||||
@@ -124,9 +122,6 @@ impl Zone {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Aspect for Zone {
|
||||
const NAME: &'static str = "Zone";
|
||||
}
|
||||
impl ZoneAspect for Zone {
|
||||
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let zone = node.get_aspect::<Zone>()?;
|
||||
|
||||
@@ -12,7 +12,6 @@ use glam::{vec3, Mat4};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::Arc;
|
||||
use stereokit_rust::system::Input;
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct EyeDatamap {
|
||||
@@ -49,58 +48,59 @@ impl EyePointer {
|
||||
pointer,
|
||||
})
|
||||
}
|
||||
// TODO: implement eyetracking in bevy_mod_openxr, then reimplement with that
|
||||
pub fn update(&self) {
|
||||
let ray = Input::get_eyes();
|
||||
self.spatial
|
||||
.set_local_transform(Mat4::from_rotation_translation(
|
||||
ray.orientation.into(),
|
||||
ray.position.into(),
|
||||
));
|
||||
{
|
||||
// Set pointer input datamap
|
||||
*self.pointer.datamap.lock() = Datamap::from_typed(EyeDatamap { eye: 2 }).unwrap();
|
||||
}
|
||||
|
||||
// send input to all the input handlers that are the closest to the ray as possible
|
||||
let rx = INPUT_HANDLER_REGISTRY
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
// filter out all the disabled handlers
|
||||
.filter(|handler| {
|
||||
let Some(node) = handler.spatial.node() else {
|
||||
return false;
|
||||
};
|
||||
node.enabled()
|
||||
})
|
||||
// ray march to all the enabled handlers' fields
|
||||
.map(|handler| {
|
||||
let result = handler.field.ray_march(Ray {
|
||||
origin: vec3(0.0, 0.0, 0.0),
|
||||
direction: vec3(0.0, 0.0, -1.0),
|
||||
space: self.spatial.clone(),
|
||||
});
|
||||
(vec![handler], result)
|
||||
})
|
||||
// make sure the field isn't at the pointer origin and that it's being hit
|
||||
.filter(|(_, result)| result.deepest_point_distance > 0.01 && result.min_distance < 0.0)
|
||||
// .inspect(|(_, result)| {
|
||||
// dbg!(result);
|
||||
// })
|
||||
// now collect all handlers that are same distance if they're the closest
|
||||
.reduce(|(mut handlers_a, result_a), (handlers_b, result_b)| {
|
||||
if (result_a.deepest_point_distance - result_b.deepest_point_distance).abs() < 0.001
|
||||
{
|
||||
// distance is basically the same
|
||||
handlers_a.extend(handlers_b);
|
||||
(handlers_a, result_a)
|
||||
} else if result_a.deepest_point_distance < result_b.deepest_point_distance {
|
||||
(handlers_a, result_a)
|
||||
} else {
|
||||
(handlers_b, result_b)
|
||||
}
|
||||
})
|
||||
.map(|(rx, _)| rx)
|
||||
.unwrap_or_default();
|
||||
self.pointer.set_handler_order(rx.iter());
|
||||
// let ray = Input::get_eyes();
|
||||
// self.spatial
|
||||
// .set_local_transform(Mat4::from_rotation_translation(
|
||||
// ray.orientation.into(),
|
||||
// ray.position.into(),
|
||||
// ));
|
||||
// {
|
||||
// // Set pointer input datamap
|
||||
// *self.pointer.datamap.lock() = Datamap::from_typed(EyeDatamap { eye: 2 }).unwrap();
|
||||
// }
|
||||
//
|
||||
// // send input to all the input handlers that are the closest to the ray as possible
|
||||
// let rx = INPUT_HANDLER_REGISTRY
|
||||
// .get_valid_contents()
|
||||
// .into_iter()
|
||||
// // filter out all the disabled handlers
|
||||
// .filter(|handler| {
|
||||
// let Some(node) = handler.spatial.node() else {
|
||||
// return false;
|
||||
// };
|
||||
// node.enabled()
|
||||
// })
|
||||
// // ray march to all the enabled handlers' fields
|
||||
// .map(|handler| {
|
||||
// let result = handler.field.ray_march(Ray {
|
||||
// origin: vec3(0.0, 0.0, 0.0),
|
||||
// direction: vec3(0.0, 0.0, -1.0),
|
||||
// space: self.spatial.clone(),
|
||||
// });
|
||||
// (vec![handler], result)
|
||||
// })
|
||||
// // make sure the field isn't at the pointer origin and that it's being hit
|
||||
// .filter(|(_, result)| result.deepest_point_distance > 0.01 && result.min_distance < 0.0)
|
||||
// // .inspect(|(_, result)| {
|
||||
// // dbg!(result);
|
||||
// // })
|
||||
// // now collect all handlers that are same distance if they're the closest
|
||||
// .reduce(|(mut handlers_a, result_a), (handlers_b, result_b)| {
|
||||
// if (result_a.deepest_point_distance - result_b.deepest_point_distance).abs() < 0.001
|
||||
// {
|
||||
// // distance is basically the same
|
||||
// handlers_a.extend(handlers_b);
|
||||
// (handlers_a, result_a)
|
||||
// } else if result_a.deepest_point_distance < result_b.deepest_point_distance {
|
||||
// (handlers_a, result_a)
|
||||
// } else {
|
||||
// (handlers_b, result_b)
|
||||
// }
|
||||
// })
|
||||
// .map(|(rx, _)| rx)
|
||||
// .unwrap_or_default();
|
||||
// self.pointer.set_handler_order(rx.iter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
use super::{get_sorted_handlers, CaptureManager, DistanceCalculator};
|
||||
use crate::{
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
data::{
|
||||
mask_matches, pulse_receiver_client, PulseSender, KEYMAPS, PULSE_RECEIVER_REGISTRY,
|
||||
},
|
||||
fields::{FieldTrait, Ray},
|
||||
input::{InputDataType, InputHandler, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
|
||||
fields::{Field, FieldTrait, Ray, EXPORTED_FIELDS},
|
||||
input::{InputDataType, InputMethod, Pointer},
|
||||
items::panel::KEYMAPS,
|
||||
spatial::Spatial,
|
||||
Node, OwnedNode,
|
||||
},
|
||||
};
|
||||
use bevy::input::keyboard::Key;
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{vec3, Mat4, Vec3};
|
||||
use mint::Vector2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slotmap::{DefaultKey, Key as SlotKey};
|
||||
use stardust_xr::values::Datamap;
|
||||
use stardust_xr::{
|
||||
schemas::dbus::{interfaces::FieldRefProxy, object_registry::ObjectRegistry},
|
||||
values::Datamap,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use stereokit_rust::system::{Input, Key};
|
||||
use xkbcommon_rs::{xkb_keymap::CompileFlags, Context, Keymap, KeymapFormat};
|
||||
|
||||
use super::{get_sorted_handlers, CaptureManager, DistanceCalculator};
|
||||
use zbus::Connection;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct MouseEvent {
|
||||
@@ -46,12 +46,14 @@ impl Default for MouseEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct KeyboardEvent {
|
||||
pub keyboard: (),
|
||||
pub xkbv1: (),
|
||||
pub keymap_id: u64,
|
||||
pub keys: Vec<i32>,
|
||||
#[zbus::proxy(
|
||||
interface = "org.stardustxr.XKBv1",
|
||||
default_service = "org.stardustxr.XKBv1"
|
||||
)]
|
||||
trait KeyboardHandler {
|
||||
async fn keymap(&self, keymap_id: u64) -> zbus::Result<()>;
|
||||
async fn key_state(&self, key: u32, pressed: bool) -> zbus::Result<()>;
|
||||
async fn reset(&self) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
@@ -62,8 +64,6 @@ pub struct MousePointer {
|
||||
pointer: Arc<InputMethod>,
|
||||
capture_manager: CaptureManager,
|
||||
mouse_datamap: MouseEvent,
|
||||
keyboard_datamap: KeyboardEvent,
|
||||
keyboard_sender: Arc<PulseSender>,
|
||||
}
|
||||
impl MousePointer {
|
||||
pub fn new() -> Result<Self> {
|
||||
@@ -75,19 +75,13 @@ impl MousePointer {
|
||||
Datamap::from_typed(MouseEvent::default())?,
|
||||
)?;
|
||||
|
||||
let context = Context::new(0).unwrap();
|
||||
let keymap = KEYMAPS.lock().insert(
|
||||
Keymap::new_from_names(context, None, CompileFlags::NO_FLAGS)
|
||||
.unwrap()
|
||||
.get_as_string(KeymapFormat::TextV1)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let keyboard_sender = PulseSender::add_to(
|
||||
&node.0,
|
||||
Datamap::from_typed(KeyboardEvent::default()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
// let context = Context::new(0).unwrap();
|
||||
// let keymap = KEYMAPS.lock().insert(
|
||||
// Keymap::new_from_names(context, None, CompileFlags::NO_FLAGS)
|
||||
// .unwrap()
|
||||
// .get_as_string(KeymapFormat::TextV1)
|
||||
// .unwrap(),
|
||||
// );
|
||||
|
||||
Ok(MousePointer {
|
||||
node,
|
||||
@@ -95,220 +89,255 @@ impl MousePointer {
|
||||
pointer,
|
||||
capture_manager: CaptureManager::default(),
|
||||
mouse_datamap: Default::default(),
|
||||
keyboard_datamap: KeyboardEvent {
|
||||
keyboard: (),
|
||||
xkbv1: (),
|
||||
keymap_id: keymap.data().as_ffi(),
|
||||
keys: vec![],
|
||||
},
|
||||
keymap,
|
||||
keyboard_sender,
|
||||
keymap: DefaultKey::default(),
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self) {
|
||||
let mouse = Input::get_mouse();
|
||||
// pub fn update(&mut self, dbus_connection: &Connection, object_registry: &ObjectRegistry) {
|
||||
// let mouse = Input::get_mouse();
|
||||
//
|
||||
// let ray = mouse.get_ray();
|
||||
// self.spatial.set_local_transform(
|
||||
// Mat4::look_to_rh(
|
||||
// Vec3::from(ray.position),
|
||||
// Vec3::from(ray.direction),
|
||||
// vec3(0.0, 1.0, 0.0),
|
||||
// )
|
||||
// .inverse(),
|
||||
// );
|
||||
// {
|
||||
// // Set pointer input datamap
|
||||
// self.mouse_datamap = MouseEvent {
|
||||
// select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
|
||||
// middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
|
||||
// context: Input::key(Key::MouseRight).is_active() as u32 as f32,
|
||||
// grab: Input::key(Key::MouseBack).is_active() as u32 as f32,
|
||||
// scroll_continuous: [0.0, mouse.scroll_change / 120.0].into(),
|
||||
// scroll_discrete: [0.0, mouse.scroll_change / 120.0].into(),
|
||||
// raw_input_events: vec![],
|
||||
// };
|
||||
// *self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
|
||||
// }
|
||||
// self.target_pointer_input();
|
||||
// self.send_keyboard_input(dbus_connection, object_registry);
|
||||
|
||||
let ray = mouse.get_ray();
|
||||
self.spatial.set_local_transform(
|
||||
Mat4::look_to_rh(
|
||||
Vec3::from(ray.position),
|
||||
Vec3::from(ray.direction),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
)
|
||||
.inverse(),
|
||||
);
|
||||
{
|
||||
// Set pointer input datamap
|
||||
self.mouse_datamap = MouseEvent {
|
||||
select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
|
||||
middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
|
||||
context: Input::key(Key::MouseRight).is_active() as u32 as f32,
|
||||
grab: Input::key(Key::MouseBack).is_active() as u32 as f32,
|
||||
scroll_continuous: [0.0, mouse.scroll_change / 120.0].into(),
|
||||
scroll_discrete: [0.0, mouse.scroll_change / 120.0].into(),
|
||||
raw_input_events: vec![],
|
||||
};
|
||||
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
|
||||
}
|
||||
self.target_pointer_input();
|
||||
self.send_keyboard_input();
|
||||
}
|
||||
fn target_pointer_input(&mut self) {
|
||||
let distance_calculator: DistanceCalculator = |space, data, field| {
|
||||
let result = field.ray_march(Ray {
|
||||
origin: vec3(0.0, 0.0, 0.0),
|
||||
direction: vec3(0.0, 0.0, -1.0),
|
||||
space: space.clone(),
|
||||
});
|
||||
let valid =
|
||||
result.deepest_point_distance > 0.0 && result.min_distance.is_sign_negative();
|
||||
valid.then_some(result.deepest_point_distance)
|
||||
};
|
||||
|
||||
self.capture_manager.update_capture(&self.pointer);
|
||||
self.capture_manager
|
||||
.set_new_capture(&self.pointer, distance_calculator);
|
||||
self.capture_manager.apply_capture(&self.pointer);
|
||||
|
||||
if self.capture_manager.capture.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sorted_handlers = get_sorted_handlers(&self.pointer, distance_calculator);
|
||||
self.pointer.set_handler_order(sorted_handlers.iter());
|
||||
// let distance_calculator: DistanceCalculator = |space, data, field| {
|
||||
// let result = field.ray_march(Ray {
|
||||
// origin: vec3(0.0, 0.0, 0.0),
|
||||
// direction: vec3(0.0, 0.0, -1.0),
|
||||
// space: space.clone(),
|
||||
// });
|
||||
// let valid =
|
||||
// result.deepest_point_distance > 0.0 && result.min_distance.is_sign_negative();
|
||||
// valid.then_some(result.deepest_point_distance)
|
||||
// };
|
||||
//
|
||||
// self.capture_manager.update_capture(&self.pointer);
|
||||
// self.capture_manager
|
||||
// .set_new_capture(&self.pointer, distance_calculator);
|
||||
// self.capture_manager.apply_capture(&self.pointer);
|
||||
//
|
||||
// if self.capture_manager.capture.is_some() {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// let sorted_handlers = get_sorted_handlers(&self.pointer, distance_calculator);
|
||||
// self.pointer.set_handler_order(sorted_handlers.iter());
|
||||
}
|
||||
|
||||
fn send_keyboard_input(&mut self) {
|
||||
let rx = PULSE_RECEIVER_REGISTRY
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
.filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask))
|
||||
.map(|rx| {
|
||||
let result = rx.field.ray_march(Ray {
|
||||
origin: vec3(0.0, 0.0, 0.0),
|
||||
direction: vec3(0.0, 0.0, -1.0),
|
||||
space: self.spatial.clone(),
|
||||
});
|
||||
(rx, result)
|
||||
})
|
||||
.filter(|(_rx, result)| {
|
||||
result.deepest_point_distance > 0.0 && result.min_distance < 0.05
|
||||
})
|
||||
.reduce(|(rx_a, result_a), (rx_b, result_b)| {
|
||||
if result_a.deepest_point_distance < result_b.deepest_point_distance {
|
||||
(rx_a, result_a)
|
||||
} else {
|
||||
(rx_b, result_b)
|
||||
}
|
||||
})
|
||||
.map(|(rx, _)| rx);
|
||||
|
||||
if let Some(rx) = rx {
|
||||
let keys = (8_u32..254)
|
||||
.map(|i| unsafe { std::mem::transmute(i) })
|
||||
.filter_map(|k| Some((map_key(k)?, Input::key(k))))
|
||||
.filter_map(|(i, k)| {
|
||||
if k.is_just_active() {
|
||||
Some(i as i32)
|
||||
} else if k.is_just_inactive() {
|
||||
Some(-(i as i32))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.keyboard_datamap.keys = keys;
|
||||
if !self.keyboard_datamap.keys.is_empty() {
|
||||
pulse_receiver_client::data(
|
||||
&rx.node.upgrade().unwrap(),
|
||||
&self.node.0,
|
||||
&Datamap::from_typed(&self.keyboard_datamap).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
pub fn send_keyboard_input(
|
||||
&mut self,
|
||||
dbus_connection: &Connection,
|
||||
object_registry: &ObjectRegistry,
|
||||
) {
|
||||
// let keyboard_handlers = object_registry.get_objects("org.stardustxr.XKBv1");
|
||||
//
|
||||
// // Spawn async task to handle keyboard input
|
||||
// tokio::spawn({
|
||||
// let keyboard_handlers = keyboard_handlers.clone();
|
||||
// let spatial = self.spatial.clone();
|
||||
// let keymap_id = self.keymap.data().as_ffi();
|
||||
// let dbus_connection = dbus_connection.clone();
|
||||
//
|
||||
// async move {
|
||||
// let mut closest_handler = None;
|
||||
// let mut closest_distance = f32::MAX;
|
||||
//
|
||||
// let mut join_set = JoinSet::new();
|
||||
// for handler in &keyboard_handlers {
|
||||
// let handler = handler.clone();
|
||||
// let dbus_connection = dbus_connection.clone();
|
||||
// join_set.spawn(async move {
|
||||
// timeout(Duration::from_millis(1), async {
|
||||
// let field_ref = handler
|
||||
// .to_typed_proxy::<FieldRefProxy>(&dbus_connection)
|
||||
// .await
|
||||
// .ok()?;
|
||||
// let uid = field_ref.uid().await.ok()?;
|
||||
// Some((handler, uid))
|
||||
// })
|
||||
// .await
|
||||
// .ok()
|
||||
// .flatten()
|
||||
// });
|
||||
// }
|
||||
// while let Some(Ok(Some((handler, field_ref_id)))) = join_set.join_next().await {
|
||||
// let exported_fields = EXPORTED_FIELDS.lock();
|
||||
// let Some(field_ref_node) = exported_fields.get(&field_ref_id) else {
|
||||
// println!("didn't find a thing :(");
|
||||
// continue;
|
||||
// };
|
||||
// // println!("still sendin stuff :)");
|
||||
// let Ok(field_ref) = field_ref_node.get_aspect::<Field>() else {
|
||||
// continue;
|
||||
// };
|
||||
// drop(exported_fields);
|
||||
//
|
||||
// let result = field_ref.ray_march(Ray {
|
||||
// origin: vec3(0.0, 0.0, 0.0),
|
||||
// direction: vec3(0.0, 0.0, -1.0),
|
||||
// space: spatial.clone(),
|
||||
// });
|
||||
//
|
||||
// if result.deepest_point_distance > 0.0
|
||||
// && result.min_distance < 0.05
|
||||
// && result.deepest_point_distance < closest_distance
|
||||
// {
|
||||
// closest_distance = result.deepest_point_distance;
|
||||
// closest_handler = Some(handler);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let Some(handler) = closest_handler else {
|
||||
// return;
|
||||
// };
|
||||
// let Ok(keyboard_handler) = handler
|
||||
// .to_typed_proxy::<KeyboardHandlerProxy>(&dbus_connection)
|
||||
// .await
|
||||
// else {
|
||||
// return;
|
||||
// };
|
||||
//
|
||||
// // Register keymap first
|
||||
// let _ = keyboard_handler.keymap(keymap_id).await;
|
||||
//
|
||||
// // Send key states
|
||||
// for i in 8_u32..254 {
|
||||
// let key = unsafe { std::mem::transmute::<u32, stereokit_rust::system::Key>(i) };
|
||||
// let Some(mapped_key) = map_key(key) else {
|
||||
// continue;
|
||||
// };
|
||||
// let input_state = Input::key(key);
|
||||
// if input_state.is_just_active() {
|
||||
// let _ = keyboard_handler.key_state(mapped_key, true).await;
|
||||
// } else if input_state.is_just_inactive() {
|
||||
// let _ = keyboard_handler.key_state(mapped_key, false).await;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
fn map_key(key: Key) -> Option<u32> {
|
||||
match key {
|
||||
Key::Backspace => Some(input_event_codes::KEY_BACKSPACE!()),
|
||||
Key::Tab => Some(input_event_codes::KEY_TAB!()),
|
||||
Key::Return => Some(input_event_codes::KEY_ENTER!()),
|
||||
Key::Shift => Some(input_event_codes::KEY_LEFTSHIFT!()),
|
||||
Key::Ctrl => Some(input_event_codes::KEY_LEFTCTRL!()),
|
||||
Key::Alt => Some(input_event_codes::KEY_LEFTALT!()),
|
||||
Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
|
||||
Key::Esc => Some(input_event_codes::KEY_ESC!()),
|
||||
Key::Space => Some(input_event_codes::KEY_SPACE!()),
|
||||
Key::End => Some(input_event_codes::KEY_END!()),
|
||||
Key::Home => Some(input_event_codes::KEY_HOME!()),
|
||||
Key::Left => Some(input_event_codes::KEY_LEFT!()),
|
||||
Key::Right => Some(input_event_codes::KEY_RIGHT!()),
|
||||
Key::Up => Some(input_event_codes::KEY_UP!()),
|
||||
Key::Down => Some(input_event_codes::KEY_DOWN!()),
|
||||
Key::PageUp => Some(input_event_codes::KEY_PAGEUP!()),
|
||||
Key::PageDown => Some(input_event_codes::KEY_PAGEDOWN!()),
|
||||
Key::PrintScreen => Some(input_event_codes::KEY_PRINT!()),
|
||||
Key::KeyInsert => Some(input_event_codes::KEY_INSERT!()),
|
||||
Key::Del => Some(input_event_codes::KEY_DELETE!()),
|
||||
Key::Key0 => Some(input_event_codes::KEY_0!()),
|
||||
Key::Key1 => Some(input_event_codes::KEY_1!()),
|
||||
Key::Key2 => Some(input_event_codes::KEY_2!()),
|
||||
Key::Key3 => Some(input_event_codes::KEY_3!()),
|
||||
Key::Key4 => Some(input_event_codes::KEY_4!()),
|
||||
Key::Key5 => Some(input_event_codes::KEY_5!()),
|
||||
Key::Key6 => Some(input_event_codes::KEY_6!()),
|
||||
Key::Key7 => Some(input_event_codes::KEY_7!()),
|
||||
Key::Key8 => Some(input_event_codes::KEY_8!()),
|
||||
Key::Key9 => Some(input_event_codes::KEY_9!()),
|
||||
Key::A => Some(input_event_codes::KEY_A!()),
|
||||
Key::B => Some(input_event_codes::KEY_B!()),
|
||||
Key::C => Some(input_event_codes::KEY_C!()),
|
||||
Key::D => Some(input_event_codes::KEY_D!()),
|
||||
Key::E => Some(input_event_codes::KEY_E!()),
|
||||
Key::F => Some(input_event_codes::KEY_F!()),
|
||||
Key::G => Some(input_event_codes::KEY_G!()),
|
||||
Key::H => Some(input_event_codes::KEY_H!()),
|
||||
Key::I => Some(input_event_codes::KEY_I!()),
|
||||
Key::J => Some(input_event_codes::KEY_J!()),
|
||||
Key::K => Some(input_event_codes::KEY_K!()),
|
||||
Key::L => Some(input_event_codes::KEY_L!()),
|
||||
Key::M => Some(input_event_codes::KEY_M!()),
|
||||
Key::N => Some(input_event_codes::KEY_N!()),
|
||||
Key::O => Some(input_event_codes::KEY_O!()),
|
||||
Key::P => Some(input_event_codes::KEY_P!()),
|
||||
Key::Q => Some(input_event_codes::KEY_Q!()),
|
||||
Key::R => Some(input_event_codes::KEY_R!()),
|
||||
Key::S => Some(input_event_codes::KEY_S!()),
|
||||
Key::T => Some(input_event_codes::KEY_T!()),
|
||||
Key::U => Some(input_event_codes::KEY_U!()),
|
||||
Key::V => Some(input_event_codes::KEY_V!()),
|
||||
Key::W => Some(input_event_codes::KEY_W!()),
|
||||
Key::X => Some(input_event_codes::KEY_X!()),
|
||||
Key::Y => Some(input_event_codes::KEY_Y!()),
|
||||
Key::Z => Some(input_event_codes::KEY_Z!()),
|
||||
Key::Numpad0 => Some(input_event_codes::KEY_NUMERIC_0!()),
|
||||
Key::Numpad1 => Some(input_event_codes::KEY_NUMERIC_1!()),
|
||||
Key::Numpad2 => Some(input_event_codes::KEY_NUMERIC_2!()),
|
||||
Key::Numpad3 => Some(input_event_codes::KEY_NUMERIC_3!()),
|
||||
Key::Numpad4 => Some(input_event_codes::KEY_NUMERIC_4!()),
|
||||
Key::Numpad5 => Some(input_event_codes::KEY_NUMERIC_5!()),
|
||||
Key::Numpad6 => Some(input_event_codes::KEY_NUMERIC_6!()),
|
||||
Key::Numpad7 => Some(input_event_codes::KEY_NUMERIC_7!()),
|
||||
Key::Numpad8 => Some(input_event_codes::KEY_NUMERIC_8!()),
|
||||
Key::Numpad9 => Some(input_event_codes::KEY_NUMERIC_9!()),
|
||||
Key::F1 => Some(input_event_codes::KEY_F1!()),
|
||||
Key::F2 => Some(input_event_codes::KEY_F2!()),
|
||||
Key::F3 => Some(input_event_codes::KEY_F3!()),
|
||||
Key::F4 => Some(input_event_codes::KEY_F4!()),
|
||||
Key::F5 => Some(input_event_codes::KEY_F5!()),
|
||||
// Key::F6 => Some(input_event_codes::KEY_F6!()),
|
||||
// Key::F7 => Some(input_event_codes::KEY_F7!()),
|
||||
// Key::F8 => Some(input_event_codes::KEY_F8!()),
|
||||
Key::F9 => Some(input_event_codes::KEY_F9!()),
|
||||
Key::F10 => Some(input_event_codes::KEY_F10!()),
|
||||
Key::F11 => Some(input_event_codes::KEY_F11!()),
|
||||
Key::F12 => Some(input_event_codes::KEY_F12!()),
|
||||
Key::Comma => Some(input_event_codes::KEY_COMMA!()),
|
||||
Key::Period => Some(input_event_codes::KEY_DOT!()),
|
||||
Key::SlashFwd => Some(input_event_codes::KEY_SLASH!()),
|
||||
Key::SlashBack => Some(input_event_codes::KEY_BACKSLASH!()),
|
||||
Key::Semicolon => Some(input_event_codes::KEY_SEMICOLON!()),
|
||||
Key::Apostrophe => Some(input_event_codes::KEY_APOSTROPHE!()),
|
||||
Key::BracketOpen => Some(input_event_codes::KEY_LEFTBRACE!()),
|
||||
Key::BracketClose => Some(input_event_codes::KEY_RIGHTBRACE!()),
|
||||
Key::Minus => Some(input_event_codes::KEY_MINUS!()),
|
||||
Key::Equals => Some(input_event_codes::KEY_EQUAL!()),
|
||||
Key::Backtick => Some(input_event_codes::KEY_GRAVE!()),
|
||||
Key::LCmd => Some(input_event_codes::KEY_LEFTMETA!()),
|
||||
Key::RCmd => Some(input_event_codes::KEY_RIGHTMETA!()),
|
||||
Key::Multiply => Some(input_event_codes::KEY_NUMERIC_STAR!()),
|
||||
Key::Add => Some(input_event_codes::KEY_KPPLUS!()),
|
||||
Key::Subtract => Some(input_event_codes::KEY_MINUS!()),
|
||||
Key::Decimal => Some(input_event_codes::KEY_DOT!()),
|
||||
Key::Divide => Some(input_event_codes::KEY_SLASH!()),
|
||||
_ => None,
|
||||
}
|
||||
fn map_key(key: ()) -> Option<u32> {
|
||||
None
|
||||
// match key {
|
||||
// Key::Backspace => Some(input_event_codes::KEY_BACKSPACE!()),
|
||||
// Key::Tab => Some(input_event_codes::KEY_TAB!()),
|
||||
// Key::Enter => Some(input_event_codes::KEY_ENTER!()),
|
||||
// Key::Shift => Some(input_event_codes::KEY_LEFTSHIFT!()),
|
||||
// Key::Control => Some(input_event_codes::KEY_LEFTCTRL!()),
|
||||
// Key::Alt => Some(input_event_codes::KEY_LEFTALT!()),
|
||||
// Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
|
||||
// Key::Escape => Some(input_event_codes::KEY_ESC!()),
|
||||
// Key::Space => Some(input_event_codes::KEY_SPACE!()),
|
||||
// Key::End => Some(input_event_codes::KEY_END!()),
|
||||
// Key::Home => Some(input_event_codes::KEY_HOME!()),
|
||||
// Key::ArrowLeft => Some(input_event_codes::KEY_LEFT!()),
|
||||
// Key::ArrowRight => Some(input_event_codes::KEY_RIGHT!()),
|
||||
// Key::ArrowUp => Some(input_event_codes::KEY_UP!()),
|
||||
// Key::ArrowDown => Some(input_event_codes::KEY_DOWN!()),
|
||||
// Key::PageUp => Some(input_event_codes::KEY_PAGEUP!()),
|
||||
// Key::PageDown => Some(input_event_codes::KEY_PAGEDOWN!()),
|
||||
// Key::PrintScreen => Some(input_event_codes::KEY_PRINT!()),
|
||||
// Key::Insert => Some(input_event_codes::KEY_INSERT!()),
|
||||
// Key::Delete => Some(input_event_codes::KEY_DELETE!()),
|
||||
// Key::Num => Some(input_event_codes::KEY_0!()),
|
||||
// Key::Key1 => Some(input_event_codes::KEY_1!()),
|
||||
// Key::Key2 => Some(input_event_codes::KEY_2!()),
|
||||
// Key::Key3 => Some(input_event_codes::KEY_3!()),
|
||||
// Key::Key4 => Some(input_event_codes::KEY_4!()),
|
||||
// Key::Key5 => Some(input_event_codes::KEY_5!()),
|
||||
// Key::Key6 => Some(input_event_codes::KEY_6!()),
|
||||
// Key::Key7 => Some(input_event_codes::KEY_7!()),
|
||||
// Key::Key8 => Some(input_event_codes::KEY_8!()),
|
||||
// Key::Key9 => Some(input_event_codes::KEY_9!()),
|
||||
// Key::A => Some(input_event_codes::KEY_A!()),
|
||||
// Key::B => Some(input_event_codes::KEY_B!()),
|
||||
// Key::C => Some(input_event_codes::KEY_C!()),
|
||||
// Key::D => Some(input_event_codes::KEY_D!()),
|
||||
// Key::E => Some(input_event_codes::KEY_E!()),
|
||||
// Key::F => Some(input_event_codes::KEY_F!()),
|
||||
// Key::G => Some(input_event_codes::KEY_G!()),
|
||||
// Key::H => Some(input_event_codes::KEY_H!()),
|
||||
// Key::I => Some(input_event_codes::KEY_I!()),
|
||||
// Key::J => Some(input_event_codes::KEY_J!()),
|
||||
// Key::K => Some(input_event_codes::KEY_K!()),
|
||||
// Key::L => Some(input_event_codes::KEY_L!()),
|
||||
// Key::M => Some(input_event_codes::KEY_M!()),
|
||||
// Key::N => Some(input_event_codes::KEY_N!()),
|
||||
// Key::O => Some(input_event_codes::KEY_O!()),
|
||||
// Key::P => Some(input_event_codes::KEY_P!()),
|
||||
// Key::Q => Some(input_event_codes::KEY_Q!()),
|
||||
// Key::R => Some(input_event_codes::KEY_R!()),
|
||||
// Key::S => Some(input_event_codes::KEY_S!()),
|
||||
// Key::T => Some(input_event_codes::KEY_T!()),
|
||||
// Key::U => Some(input_event_codes::KEY_U!()),
|
||||
// Key::V => Some(input_event_codes::KEY_V!()),
|
||||
// Key::W => Some(input_event_codes::KEY_W!()),
|
||||
// Key::X => Some(input_event_codes::KEY_X!()),
|
||||
// Key::Y => Some(input_event_codes::KEY_Y!()),
|
||||
// Key::Z => Some(input_event_codes::KEY_Z!()),
|
||||
// Key::Numpad0 => Some(input_event_codes::KEY_NUMERIC_0!()),
|
||||
// Key::Numpad1 => Some(input_event_codes::KEY_NUMERIC_1!()),
|
||||
// Key::Numpad2 => Some(input_event_codes::KEY_NUMERIC_2!()),
|
||||
// Key::Numpad3 => Some(input_event_codes::KEY_NUMERIC_3!()),
|
||||
// Key::Numpad4 => Some(input_event_codes::KEY_NUMERIC_4!()),
|
||||
// Key::Numpad5 => Some(input_event_codes::KEY_NUMERIC_5!()),
|
||||
// Key::Numpad6 => Some(input_event_codes::KEY_NUMERIC_6!()),
|
||||
// Key::Numpad7 => Some(input_event_codes::KEY_NUMERIC_7!()),
|
||||
// Key::Numpad8 => Some(input_event_codes::KEY_NUMERIC_8!()),
|
||||
// Key::Numpad9 => Some(input_event_codes::KEY_NUMERIC_9!()),
|
||||
// Key::F1 => Some(input_event_codes::KEY_F1!()),
|
||||
// Key::F2 => Some(input_event_codes::KEY_F2!()),
|
||||
// Key::F3 => Some(input_event_codes::KEY_F3!()),
|
||||
// Key::F4 => Some(input_event_codes::KEY_F4!()),
|
||||
// Key::F5 => Some(input_event_codes::KEY_F5!()),
|
||||
// // Key::F6 => Some(input_event_codes::KEY_F6!()),
|
||||
// // Key::F7 => Some(input_event_codes::KEY_F7!()),
|
||||
// // Key::F8 => Some(input_event_codes::KEY_F8!()),
|
||||
// Key::F9 => Some(input_event_codes::KEY_F9!()),
|
||||
// Key::F10 => Some(input_event_codes::KEY_F10!()),
|
||||
// Key::F11 => Some(input_event_codes::KEY_F11!()),
|
||||
// Key::F12 => Some(input_event_codes::KEY_F12!()),
|
||||
// Key::Comma => Some(input_event_codes::KEY_COMMA!()),
|
||||
// Key::Period => Some(input_event_codes::KEY_DOT!()),
|
||||
// Key::SlashFwd => Some(input_event_codes::KEY_SLASH!()),
|
||||
// Key::SlashBack => Some(input_event_codes::KEY_BACKSLASH!()),
|
||||
// Key::Semicolon => Some(input_event_codes::KEY_SEMICOLON!()),
|
||||
// Key::Apostrophe => Some(input_event_codes::KEY_APOSTROPHE!()),
|
||||
// Key::BracketOpen => Some(input_event_codes::KEY_LEFTBRACE!()),
|
||||
// Key::BracketClose => Some(input_event_codes::KEY_RIGHTBRACE!()),
|
||||
// Key::Minus => Some(input_event_codes::KEY_MINUS!()),
|
||||
// Key::Equals => Some(input_event_codes::KEY_EQUAL!()),
|
||||
// Key::Backtick => Some(input_event_codes::KEY_GRAVE!()),
|
||||
// Key::LCmd => Some(input_event_codes::KEY_LEFTMETA!()),
|
||||
// Key::RCmd => Some(input_event_codes::KEY_RIGHTMETA!()),
|
||||
// Key::Multiply => Some(input_event_codes::KEY_NUMERIC_STAR!()),
|
||||
// Key::Add => Some(input_event_codes::KEY_KPPLUS!()),
|
||||
// Key::Subtract => Some(input_event_codes::KEY_MINUS!()),
|
||||
// Key::Decimal => Some(input_event_codes::KEY_DOT!()),
|
||||
// Key::Divide => Some(input_event_codes::KEY_SLASH!()),
|
||||
// _ => None,
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::{get_sorted_handlers, CaptureManager};
|
||||
use crate::{
|
||||
bevy_plugin::{DbusConnection, InputUpdate},
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
fields::{Field, FieldTrait},
|
||||
@@ -8,19 +9,40 @@ use crate::{
|
||||
Node, OwnedNode,
|
||||
},
|
||||
objects::{ObjectHandle, SpatialRef},
|
||||
DefaultMaterial,
|
||||
};
|
||||
use bevy::{
|
||||
app::{App, Plugin},
|
||||
asset::{embedded_asset, AssetServer, Assets, Handle},
|
||||
color::LinearRgba,
|
||||
gltf::GltfAssetLabel,
|
||||
pbr::MeshMaterial3d,
|
||||
prelude::{
|
||||
Children, Commands, Component, IntoSystemConfigs as _, Mesh, Query, Res, ResMut, Transform,
|
||||
},
|
||||
scene::SceneRoot,
|
||||
utils::default,
|
||||
};
|
||||
use bevy_mod_openxr::{
|
||||
helper_traits::{ToQuat, ToVec2, ToVec3},
|
||||
openxr_session_running,
|
||||
resources::{OxrFrameState, Pipelined},
|
||||
session::OxrSession,
|
||||
spaces::{OxrSpaceExt, OxrSpaceLocationFlags},
|
||||
};
|
||||
use bevy_mod_xr::{
|
||||
hands::HandSide,
|
||||
session::XrSessionCreated,
|
||||
spaces::{XrPrimaryReferenceSpace, XrSpace},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{Mat4, Vec2, Vec3};
|
||||
use once_cell::sync::OnceCell;
|
||||
use openxr::{ActionSet, Posef};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::Arc;
|
||||
use stereokit_rust::{
|
||||
material::Material,
|
||||
model::Model,
|
||||
sk::MainThreadToken,
|
||||
system::{Handed, Input},
|
||||
util::Color128,
|
||||
};
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
use tracing::{debug_span, error};
|
||||
use zbus::Connection;
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
@@ -32,97 +54,217 @@ struct ControllerDatamap {
|
||||
scroll: Vec2,
|
||||
}
|
||||
|
||||
pub struct SkController {
|
||||
object_handle: ObjectHandle<SpatialRef>,
|
||||
input: Arc<InputMethod>,
|
||||
handed: Handed,
|
||||
model: Model,
|
||||
material: Material,
|
||||
capture_manager: CaptureManager,
|
||||
datamap: ControllerDatamap,
|
||||
}
|
||||
impl SkController {
|
||||
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
|
||||
let (spatial, object_handle) = SpatialRef::create(
|
||||
connection,
|
||||
&("/org/stardustxr/Controller/".to_string()
|
||||
+ match handed {
|
||||
Handed::Left => "left",
|
||||
_ => "right",
|
||||
}),
|
||||
pub struct StardustControllerPlugin;
|
||||
impl Plugin for StardustControllerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
embedded_asset!(app, "src/objects/input", "cursor.glb");
|
||||
app.add_systems(XrSessionCreated, spawn_controllers);
|
||||
app.add_systems(
|
||||
InputUpdate,
|
||||
update_controllers.run_if(openxr_session_running),
|
||||
);
|
||||
let model = Model::copy(Model::from_memory(
|
||||
"cursor.glb",
|
||||
include_bytes!("cursor.glb"),
|
||||
None,
|
||||
)?);
|
||||
let model_nodes = model.get_nodes();
|
||||
let mut model_node = model_nodes.visuals().next().unwrap();
|
||||
let material = Material::copy(&model_node.get_material().unwrap());
|
||||
model_node.material(&material);
|
||||
let tip = InputDataType::Tip(Tip::default());
|
||||
let input = InputMethod::add_to(
|
||||
&spatial.node().unwrap(),
|
||||
tip,
|
||||
Datamap::from_typed(ControllerDatamap::default())?,
|
||||
)?;
|
||||
Ok(SkController {
|
||||
object_handle,
|
||||
input,
|
||||
handed,
|
||||
model,
|
||||
material,
|
||||
capture_manager: CaptureManager::default(),
|
||||
datamap: Default::default(),
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, token: &MainThreadToken) {
|
||||
let controller = Input::controller(self.handed);
|
||||
let input_node = self.input.spatial.node().unwrap();
|
||||
input_node.set_enabled(controller.tracked.is_active());
|
||||
if input_node.enabled() {
|
||||
let world_transform = Mat4::from_rotation_translation(
|
||||
controller.aim.orientation.into(),
|
||||
controller.aim.position.into(),
|
||||
);
|
||||
self.material
|
||||
.color_tint(if self.capture_manager.capture.is_none() {
|
||||
Color128::new_rgb(1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color128::new_rgb(0.0, 1.0, 0.75)
|
||||
});
|
||||
self.model.draw(
|
||||
token,
|
||||
world_transform * Mat4::from_scale(Vec3::ONE * 0.02),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
self.input.spatial.set_local_transform(world_transform);
|
||||
}
|
||||
}
|
||||
|
||||
self.datamap = ControllerDatamap {
|
||||
select: controller.trigger,
|
||||
middle: controller.stick_click.is_active() as u32 as f32,
|
||||
context: controller.is_x2_pressed() as u32 as f32,
|
||||
grab: controller.grip,
|
||||
scroll: controller.stick.into(),
|
||||
fn update_controllers(
|
||||
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||
mut query: Query<(&mut SkController, &mut Transform)>,
|
||||
time: Res<OxrFrameState>,
|
||||
base_space: Res<XrPrimaryReferenceSpace>,
|
||||
session: ResMut<OxrSession>,
|
||||
pipelined: Option<Res<Pipelined>>,
|
||||
) {
|
||||
for (mut controller, mut transform) in query.iter_mut() {
|
||||
let input_node = controller.input.spatial.node().unwrap();
|
||||
let time = if pipelined.is_some() {
|
||||
openxr::Time::from_nanos(
|
||||
time.predicted_display_time.as_nanos() + time.predicted_display_period.as_nanos(),
|
||||
)
|
||||
} else {
|
||||
time.predicted_display_time
|
||||
};
|
||||
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
|
||||
let location = (|| {
|
||||
let location = match session.locate_space(
|
||||
&XrSpace::from_raw_openxr_space(controller.space.as_raw()),
|
||||
&base_space,
|
||||
time,
|
||||
) {
|
||||
Err(err) => {
|
||||
error!("issues locating controller space: {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(val) => val,
|
||||
};
|
||||
let flags = OxrSpaceLocationFlags(location.location_flags);
|
||||
|
||||
input_node.set_enabled(flags.pos_tracked() && flags.rot_tracked());
|
||||
if flags.pos_valid() && flags.rot_valid() {
|
||||
Some(Mat4::from_rotation_translation(
|
||||
location.pose.orientation.to_quat(),
|
||||
location.pose.position.to_vec3(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})()
|
||||
.unwrap_or(Mat4::IDENTITY);
|
||||
if input_node.enabled() {
|
||||
let world_transform = location;
|
||||
if let Some(mat) = controller.material.get().and_then(|v| mats.get_mut(v)) {
|
||||
mat.color = if controller.capture_manager.capture.is_none() {
|
||||
LinearRgba::rgb(1.0, 1.0, 1.0)
|
||||
} else {
|
||||
LinearRgba::rgb(0.0, 1.0, 0.75)
|
||||
}
|
||||
.into();
|
||||
}
|
||||
|
||||
*transform =
|
||||
Transform::from_matrix(world_transform * Mat4::from_scale(Vec3::ONE * 0.02));
|
||||
controller
|
||||
.input
|
||||
.spatial
|
||||
.set_local_transform(world_transform);
|
||||
}
|
||||
// controller.datamap.select = controller
|
||||
// .actions
|
||||
// .trigger
|
||||
// .state(&session, openxr::Path::NULL)
|
||||
// .map(|v| v.current_state)
|
||||
// .unwrap_or_default();
|
||||
// controller.datamap.grab = controller
|
||||
// .actions
|
||||
// .grip
|
||||
// .state(&session, openxr::Path::NULL)
|
||||
// .map(|v| v.current_state)
|
||||
// .unwrap_or_default();
|
||||
// controller.datamap.scroll = controller
|
||||
// .actions
|
||||
// .stick
|
||||
// .state(&session, openxr::Path::NULL)
|
||||
// .map(|v| v.current_state.to_vec2())
|
||||
// .unwrap_or_default();
|
||||
*controller.input.datamap.lock() = Datamap::from_typed(&controller.datamap).unwrap();
|
||||
|
||||
// remove the capture when it's removed from captures list
|
||||
let distance_calculator = |space: &Arc<Spatial>, _data: &InputDataType, field: &Field| {
|
||||
Some(field.distance(space, [0.0; 3].into()).abs())
|
||||
};
|
||||
|
||||
self.capture_manager.update_capture(&self.input);
|
||||
self.capture_manager
|
||||
.set_new_capture(&self.input, distance_calculator);
|
||||
self.capture_manager.apply_capture(&self.input);
|
||||
let input = controller.input.clone();
|
||||
controller.capture_manager.update_capture(&input);
|
||||
controller
|
||||
.capture_manager
|
||||
.set_new_capture(&input, distance_calculator);
|
||||
controller.capture_manager.apply_capture(&input);
|
||||
|
||||
if self.capture_manager.capture.is_some() {
|
||||
if controller.capture_manager.capture.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
|
||||
self.input.set_handler_order(sorted_handlers.iter());
|
||||
let sorted_handlers = get_sorted_handlers(&input, distance_calculator);
|
||||
controller.input.set_handler_order(sorted_handlers.iter());
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_controllers(
|
||||
connection: Res<DbusConnection>,
|
||||
asset_server: Res<AssetServer>,
|
||||
session: Res<OxrSession>,
|
||||
mut cmds: Commands,
|
||||
) {
|
||||
let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset("embedded://cursor.glb"));
|
||||
for handed in [HandSide::Left, HandSide::Right] {
|
||||
let side = match handed {
|
||||
HandSide::Left => "left",
|
||||
HandSide::Right => "right",
|
||||
};
|
||||
let _span = debug_span!("create SpatialRef").entered();
|
||||
let (spatial, object_handle) = SpatialRef::create(
|
||||
&connection,
|
||||
&("/org/stardustxr/Controller/".to_string() + side),
|
||||
);
|
||||
drop(_span);
|
||||
let tip = InputDataType::Tip(Tip::default());
|
||||
|
||||
let _span = debug_span!("create input method").entered();
|
||||
let Ok(input) = (|| -> color_eyre::Result<Arc<InputMethod>> {
|
||||
Ok(InputMethod::add_to(
|
||||
&spatial.node().unwrap(),
|
||||
tip,
|
||||
Datamap::from_typed(ControllerDatamap::default())?,
|
||||
)?)
|
||||
})() else {
|
||||
continue;
|
||||
};
|
||||
drop(_span);
|
||||
let _span = debug_span!("create actions").entered();
|
||||
let actions = {
|
||||
let set = session
|
||||
.instance()
|
||||
.create_action_set(
|
||||
&format!("controller-{side}"),
|
||||
&format!("{side} controller"),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
Actions {
|
||||
set: set.clone(),
|
||||
trigger: set
|
||||
.create_action(&format!("trigger-{side}"), &format!("{side} trigger"), &[])
|
||||
.unwrap(),
|
||||
grip: set
|
||||
.create_action(&format!("grip-{side}"), &format!("{side} grip"), &[])
|
||||
.unwrap(),
|
||||
stick: set
|
||||
.create_action(&format!("stick-{side}"), &format!("{side} stick"), &[])
|
||||
.unwrap(),
|
||||
pose: set
|
||||
.create_action(&format!("pose-{side}"), &format!("{side} pose"), &[])
|
||||
.unwrap(),
|
||||
}
|
||||
};
|
||||
drop(_span);
|
||||
let _span = debug_span!("spawn").entered();
|
||||
cmds.spawn((
|
||||
SceneRoot(handle.clone()),
|
||||
SkController {
|
||||
object_handle,
|
||||
input,
|
||||
handed,
|
||||
material: OnceCell::new(),
|
||||
datamap: Default::default(),
|
||||
space: actions
|
||||
.pose
|
||||
.create_space(
|
||||
session.deref().deref().clone(),
|
||||
openxr::Path::NULL,
|
||||
Posef::IDENTITY,
|
||||
)
|
||||
.unwrap(),
|
||||
actions,
|
||||
capture_manager: default(),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[require(Transform)]
|
||||
pub struct SkController {
|
||||
object_handle: ObjectHandle<SpatialRef>,
|
||||
input: Arc<InputMethod>,
|
||||
handed: HandSide,
|
||||
material: OnceCell<Handle<DefaultMaterial>>,
|
||||
datamap: ControllerDatamap,
|
||||
space: openxr::Space,
|
||||
actions: Actions,
|
||||
capture_manager: CaptureManager,
|
||||
}
|
||||
struct Actions {
|
||||
set: openxr::ActionSet,
|
||||
trigger: openxr::Action<f32>,
|
||||
grip: openxr::Action<f32>,
|
||||
stick: openxr::Action<openxr::Vector2f>,
|
||||
pose: openxr::Action<openxr::Posef>,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::bevy_plugin::{DbusConnection, InputUpdate};
|
||||
use crate::core::client::INTERNAL_CLIENT;
|
||||
use crate::nodes::fields::{Field, FieldTrait};
|
||||
use crate::nodes::input::{InputDataType, InputHandler, INPUT_HANDLER_REGISTRY};
|
||||
@@ -8,135 +9,128 @@ use crate::nodes::{
|
||||
Node,
|
||||
};
|
||||
use crate::objects::{ObjectHandle, SpatialRef};
|
||||
use crate::DefaultMaterial;
|
||||
use bevy::app::{Plugin, PostUpdate};
|
||||
use bevy::asset::{AssetServer, Assets, Handle};
|
||||
use bevy::prelude::{
|
||||
Commands, Component, Entity, Gizmos, IntoSystemConfigs as _, Query, Res, ResMut,
|
||||
};
|
||||
use bevy::utils::default;
|
||||
use bevy_mod_openxr::helper_traits::{ToQuat, ToVec3};
|
||||
use bevy_mod_openxr::openxr_session_running;
|
||||
use bevy_mod_openxr::resources::{OxrFrameState, Pipelined};
|
||||
use bevy_mod_openxr::session::OxrSession;
|
||||
use bevy_mod_openxr::spaces::OxrSpaceLocationFlags;
|
||||
use bevy_mod_xr::hands::{HandBone, HandSide};
|
||||
use bevy_mod_xr::session::XrSessionCreated;
|
||||
use bevy_mod_xr::spaces::XrPrimaryReferenceSpace;
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{Mat4, Quat, Vec3};
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::f32::INFINITY;
|
||||
use std::sync::Arc;
|
||||
use stereokit_rust::sk::{DisplayMode, MainThreadToken, Sk};
|
||||
use stereokit_rust::system::{HandJoint, HandSource, Handed, Input, LinePoint, Lines};
|
||||
use stereokit_rust::util::Color128;
|
||||
use tracing::error;
|
||||
use zbus::Connection;
|
||||
|
||||
use super::{get_sorted_handlers, CaptureManager};
|
||||
|
||||
fn convert_joint(joint: HandJoint) -> Joint {
|
||||
Joint {
|
||||
position: Vec3::from(joint.position).into(),
|
||||
rotation: Quat::from(joint.orientation).into(),
|
||||
radius: joint.radius,
|
||||
distance: 0.0,
|
||||
fn update_joint(joint: &mut Joint, oxr_joint: openxr::HandJointLocation) {
|
||||
let flags = OxrSpaceLocationFlags(oxr_joint.location_flags);
|
||||
if flags.pos_valid() && flags.rot_valid() {
|
||||
*joint = convert_joint(oxr_joint);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
struct HandDatamap {
|
||||
pinch_strength: f32,
|
||||
grab_strength: f32,
|
||||
}
|
||||
|
||||
pub struct SkHand {
|
||||
_node: OwnedNode,
|
||||
palm_spatial: Arc<Spatial>,
|
||||
palm_object: ObjectHandle<SpatialRef>,
|
||||
handed: Handed,
|
||||
input: Arc<InputMethod>,
|
||||
capture_manager: CaptureManager,
|
||||
datamap: HandDatamap,
|
||||
}
|
||||
impl SkHand {
|
||||
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
|
||||
let (palm_spatial, palm_object) = SpatialRef::create(
|
||||
connection,
|
||||
&("/org/stardustxr/Hand/".to_string()
|
||||
+ match handed {
|
||||
Handed::Left => "left",
|
||||
_ => "right",
|
||||
} + "/palm"),
|
||||
pub struct StardustHandPlugin;
|
||||
impl Plugin for StardustHandPlugin {
|
||||
fn build(&self, app: &mut bevy::prelude::App) {
|
||||
app.add_systems(XrSessionCreated, create_hands);
|
||||
app.add_systems(
|
||||
InputUpdate,
|
||||
(
|
||||
update_hands.run_if(openxr_session_running),
|
||||
draw_hand_gizmos,
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
let _node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
|
||||
Spatial::add_to(&_node.0, None, Mat4::IDENTITY, false);
|
||||
let hand = InputDataType::Hand(Hand {
|
||||
right: handed == Handed::Right,
|
||||
..Default::default()
|
||||
});
|
||||
let datamap = Datamap::from_typed(HandDatamap::default())?;
|
||||
let input = InputMethod::add_to(&_node.0, hand, datamap)?;
|
||||
|
||||
Input::hand_visible(handed, false);
|
||||
Ok(SkHand {
|
||||
_node,
|
||||
palm_spatial,
|
||||
palm_object,
|
||||
handed,
|
||||
input,
|
||||
capture_manager: CaptureManager::default(),
|
||||
datamap: Default::default(),
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
|
||||
let sk_hand = Input::hand(self.handed);
|
||||
let real_hand = Input::hand_source(self.handed) as u32 == HandSource::Articulated as u32;
|
||||
if let InputDataType::Hand(hand) = &mut *self.input.data.lock() {
|
||||
let input_node = self.input.spatial.node().unwrap();
|
||||
input_node.set_enabled(
|
||||
(real_hand || sk.get_active_display_mode() == DisplayMode::Flatscreen)
|
||||
&& sk_hand.tracked.is_active(),
|
||||
);
|
||||
if input_node.enabled() {
|
||||
hand.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
|
||||
hand.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
|
||||
hand.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
|
||||
hand.thumb.metacarpal = convert_joint(sk_hand.fingers[0][1]);
|
||||
}
|
||||
|
||||
for (finger, mut sk_finger) in [
|
||||
(&mut hand.index, sk_hand.fingers[1]),
|
||||
(&mut hand.middle, sk_hand.fingers[2]),
|
||||
(&mut hand.ring, sk_hand.fingers[3]),
|
||||
(&mut hand.little, sk_hand.fingers[4]),
|
||||
] {
|
||||
sk_finger[4].radius = 0.0;
|
||||
finger.tip = convert_joint(sk_finger[4]);
|
||||
finger.distal = convert_joint(sk_finger[3]);
|
||||
finger.intermediate = convert_joint(sk_finger[2]);
|
||||
finger.proximal = convert_joint(sk_finger[1]);
|
||||
finger.metacarpal = convert_joint(sk_finger[0]);
|
||||
}
|
||||
fn draw_hand_gizmos(mut gizmos: Gizmos, query: Query<&SkHand>) {
|
||||
for hand in query.iter() {
|
||||
gizmos.axes(hand.palm_spatial.global_transform(), 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
hand.palm.position = Vec3::from(sk_hand.palm.position).into();
|
||||
hand.palm.rotation = Quat::from(sk_hand.palm.orientation).into();
|
||||
hand.palm.radius =
|
||||
(sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5;
|
||||
|
||||
self.palm_spatial
|
||||
.set_local_transform(Mat4::from_rotation_translation(
|
||||
hand.palm.rotation.into(),
|
||||
hand.palm.position.into(),
|
||||
));
|
||||
|
||||
hand.wrist.position = Vec3::from(sk_hand.wrist.position).into();
|
||||
hand.wrist.rotation = Quat::from(sk_hand.wrist.orientation).into();
|
||||
hand.wrist.radius =
|
||||
(sk_hand.fingers[0][0].radius + sk_hand.fingers[4][0].radius) * 0.5;
|
||||
|
||||
hand.elbow = None;
|
||||
|
||||
self.draw(
|
||||
token,
|
||||
if self.capture_manager.capture.is_none() {
|
||||
Color128::new_rgb(1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color128::new_rgb(0.0, 1.0, 0.75)
|
||||
},
|
||||
hand,
|
||||
fn update_hands(
|
||||
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||
mut query: Query<&mut SkHand>,
|
||||
time: Res<OxrFrameState>,
|
||||
base_space: Res<XrPrimaryReferenceSpace>,
|
||||
session: ResMut<OxrSession>,
|
||||
pipelined: Option<Res<Pipelined>>,
|
||||
) {
|
||||
let time = if pipelined.is_some() {
|
||||
openxr::Time::from_nanos(
|
||||
time.predicted_display_time.as_nanos() + time.predicted_display_period.as_nanos(),
|
||||
)
|
||||
} else {
|
||||
time.predicted_display_time
|
||||
};
|
||||
for mut hand in &mut query {
|
||||
let joints = session
|
||||
.locate_hand_joints(&hand.hand_tracker, &base_space, time)
|
||||
.unwrap();
|
||||
if let InputDataType::Hand(hand_input) = &mut *hand.input.data.lock() {
|
||||
let input_node = hand.input.spatial.node().unwrap();
|
||||
input_node.set_enabled(joints.is_some());
|
||||
if let Some(joints) = joints.as_ref() {
|
||||
update_joint(
|
||||
&mut hand_input.thumb.tip,
|
||||
joints[HandBone::ThumbTip as usize],
|
||||
);
|
||||
update_joint(
|
||||
&mut hand_input.thumb.distal,
|
||||
joints[HandBone::ThumbDistal as usize],
|
||||
);
|
||||
update_joint(
|
||||
&mut hand_input.thumb.proximal,
|
||||
joints[HandBone::ThumbProximal as usize],
|
||||
);
|
||||
update_joint(
|
||||
&mut hand_input.thumb.metacarpal,
|
||||
joints[HandBone::ThumbMetacarpal as usize],
|
||||
);
|
||||
|
||||
for (finger, finger_index) in [
|
||||
(&mut hand_input.index, 6),
|
||||
(&mut hand_input.middle, 11),
|
||||
(&mut hand_input.ring, 16),
|
||||
(&mut hand_input.little, 21),
|
||||
] {
|
||||
update_joint(&mut finger.tip, joints[finger_index + 4]);
|
||||
update_joint(&mut finger.distal, joints[finger_index + 3]);
|
||||
update_joint(&mut finger.intermediate, joints[finger_index + 2]);
|
||||
update_joint(&mut finger.proximal, joints[finger_index + 1]);
|
||||
update_joint(&mut finger.metacarpal, joints[finger_index]);
|
||||
}
|
||||
update_joint(&mut hand_input.palm, joints[HandBone::Palm as usize]);
|
||||
hand.palm_spatial
|
||||
.set_local_transform(Mat4::from_rotation_translation(
|
||||
hand_input.palm.rotation.into(),
|
||||
hand_input.palm.position.into(),
|
||||
));
|
||||
update_joint(&mut hand_input.wrist, joints[HandBone::Wrist as usize]);
|
||||
|
||||
hand_input.elbow = None;
|
||||
}
|
||||
}
|
||||
self.datamap.pinch_strength = sk_hand.pinch_activation;
|
||||
self.datamap.grab_strength = sk_hand.grip_activation;
|
||||
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
|
||||
|
||||
if let Some(joints) = joints.as_ref() {
|
||||
hand.datamap.pinch_strength = pinch_activation(joints);
|
||||
hand.datamap.grab_strength = grip_activation(joints);
|
||||
*hand.input.datamap.lock() = Datamap::from_typed(&hand.datamap).unwrap();
|
||||
}
|
||||
let distance_calculator = |space: &Arc<Spatial>, data: &InputDataType, field: &Field| {
|
||||
let InputDataType::Hand(hand) = data else {
|
||||
return None;
|
||||
@@ -154,84 +148,148 @@ impl SkHand {
|
||||
)
|
||||
};
|
||||
|
||||
self.capture_manager.update_capture(&self.input);
|
||||
self.capture_manager
|
||||
.set_new_capture(&self.input, distance_calculator);
|
||||
self.capture_manager.apply_capture(&self.input);
|
||||
let input = hand.input.clone();
|
||||
hand.capture_manager.update_capture(&input);
|
||||
hand.capture_manager
|
||||
.set_new_capture(&input, distance_calculator);
|
||||
hand.capture_manager.apply_capture(&input);
|
||||
|
||||
if self.capture_manager.capture.is_some() {
|
||||
if hand.capture_manager.capture.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
|
||||
self.input.set_handler_order(sorted_handlers.iter());
|
||||
}
|
||||
|
||||
fn draw(&self, token: &MainThreadToken, color: Color128, hand: &Hand) {
|
||||
// thumb
|
||||
Lines::add_list(
|
||||
token,
|
||||
&[
|
||||
joint_to_line_point(&hand.thumb.tip, color),
|
||||
joint_to_line_point(&hand.thumb.distal, color),
|
||||
joint_to_line_point(&hand.thumb.proximal, color),
|
||||
joint_to_line_point(&hand.thumb.metacarpal, color),
|
||||
],
|
||||
);
|
||||
// index
|
||||
Lines::add_list(
|
||||
token,
|
||||
&[
|
||||
joint_to_line_point(&hand.index.tip, color),
|
||||
joint_to_line_point(&hand.index.distal, color),
|
||||
joint_to_line_point(&hand.index.intermediate, color),
|
||||
joint_to_line_point(&hand.index.proximal, color),
|
||||
joint_to_line_point(&hand.index.metacarpal, color),
|
||||
],
|
||||
);
|
||||
// middle
|
||||
Lines::add_list(
|
||||
token,
|
||||
&[
|
||||
joint_to_line_point(&hand.middle.tip, color),
|
||||
joint_to_line_point(&hand.middle.distal, color),
|
||||
joint_to_line_point(&hand.middle.intermediate, color),
|
||||
joint_to_line_point(&hand.middle.proximal, color),
|
||||
joint_to_line_point(&hand.middle.metacarpal, color),
|
||||
],
|
||||
);
|
||||
// ring
|
||||
Lines::add_list(
|
||||
token,
|
||||
&[
|
||||
joint_to_line_point(&hand.ring.tip, color),
|
||||
joint_to_line_point(&hand.ring.distal, color),
|
||||
joint_to_line_point(&hand.ring.intermediate, color),
|
||||
joint_to_line_point(&hand.ring.proximal, color),
|
||||
joint_to_line_point(&hand.ring.metacarpal, color),
|
||||
],
|
||||
);
|
||||
|
||||
// palm
|
||||
Lines::add_list(
|
||||
token,
|
||||
&[
|
||||
joint_to_line_point(&hand.wrist, color),
|
||||
joint_to_line_point(&hand.thumb.metacarpal, color),
|
||||
joint_to_line_point(&hand.index.metacarpal, color),
|
||||
joint_to_line_point(&hand.middle.metacarpal, color),
|
||||
joint_to_line_point(&hand.ring.metacarpal, color),
|
||||
joint_to_line_point(&hand.little.metacarpal, color),
|
||||
joint_to_line_point(&hand.wrist, color),
|
||||
],
|
||||
);
|
||||
let sorted_handlers = get_sorted_handlers(&hand.input, distance_calculator);
|
||||
hand.input.set_handler_order(sorted_handlers.iter());
|
||||
}
|
||||
}
|
||||
|
||||
fn joint_to_line_point(joint: &Joint, color: Color128) -> LinePoint {
|
||||
LinePoint {
|
||||
pt: Vec3::from(joint.position).into(),
|
||||
thickness: joint.radius * 2.0,
|
||||
color: color.into(),
|
||||
const PINCH_MAX: f32 = 0.11;
|
||||
const PINCH_ACTIVACTION_DISTANCE: f32 = 0.01;
|
||||
// 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 =
|
||||
joints[HandBone::ThumbTip as usize].radius + joints[HandBone::IndexTip as usize].radius;
|
||||
let pinch_dist = joints[HandBone::ThumbTip as usize]
|
||||
.pose
|
||||
.position
|
||||
.to_vec3()
|
||||
.distance(joints[HandBone::IndexTip as usize].pose.position.to_vec3())
|
||||
- combined_radius;
|
||||
(1.0 - ((pinch_dist - PINCH_ACTIVACTION_DISTANCE) / (PINCH_MAX - PINCH_ACTIVACTION_DISTANCE)))
|
||||
.clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
const GRIP_MAX: f32 = 0.11;
|
||||
const GRIP_ACTIVACTION_DISTANCE: f32 = 0.01;
|
||||
// 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
|
||||
+ joints[HandBone::RingMetacarpal as usize].radius;
|
||||
let grip_dist = joints[HandBone::RingTip as usize]
|
||||
.pose
|
||||
.position
|
||||
.to_vec3()
|
||||
.distance(
|
||||
joints[HandBone::RingMetacarpal as usize]
|
||||
.pose
|
||||
.position
|
||||
.to_vec3(),
|
||||
) - combined_radius;
|
||||
(1.0 - ((grip_dist - GRIP_ACTIVACTION_DISTANCE) / (GRIP_MAX - GRIP_ACTIVACTION_DISTANCE)))
|
||||
.clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
fn create_hands(connection: Res<DbusConnection>, session: Res<OxrSession>, mut cmds: Commands) {
|
||||
for handed in [HandSide::Left, HandSide::Right] {
|
||||
let hand = (|| -> color_eyre::Result<_> {
|
||||
let side = match handed {
|
||||
HandSide::Left => "left",
|
||||
HandSide::Right => "right",
|
||||
};
|
||||
let (palm_spatial, palm_object) = SpatialRef::create(
|
||||
&connection,
|
||||
&("/org/stardustxr/Hand/".to_string() + side + "/palm"),
|
||||
);
|
||||
let _node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
|
||||
Spatial::add_to(&_node.0, None, Mat4::IDENTITY, false);
|
||||
let hand = InputDataType::Hand(Hand {
|
||||
right: matches!(handed, HandSide::Right),
|
||||
..Default::default()
|
||||
});
|
||||
let datamap = Datamap::from_typed(HandDatamap::default())?;
|
||||
let input = InputMethod::add_to(&_node.0, hand, datamap)?;
|
||||
|
||||
let tracker = session.create_hand_tracker(match handed {
|
||||
HandSide::Left => openxr::Hand::LEFT,
|
||||
HandSide::Right => openxr::Hand::RIGHT,
|
||||
})?;
|
||||
|
||||
Ok(SkHand {
|
||||
_node,
|
||||
palm_spatial,
|
||||
palm_object,
|
||||
handed,
|
||||
input,
|
||||
capture_manager: default(),
|
||||
datamap: Default::default(),
|
||||
material: OnceCell::new(),
|
||||
vis_entity: OnceCell::new(),
|
||||
hand_tracker: tracker,
|
||||
})
|
||||
})();
|
||||
let hand = match hand {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
error!("error while creating hand: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
cmds.spawn(hand);
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_joint(joint: openxr::HandJointLocation) -> Joint {
|
||||
Joint {
|
||||
position: joint.pose.position.to_vec3().into(),
|
||||
rotation: joint.pose.orientation.to_quat().into(),
|
||||
radius: joint.radius,
|
||||
distance: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
struct HandDatamap {
|
||||
pinch_strength: f32,
|
||||
grab_strength: f32,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SkHand {
|
||||
_node: OwnedNode,
|
||||
palm_spatial: Arc<Spatial>,
|
||||
palm_object: ObjectHandle<SpatialRef>,
|
||||
handed: HandSide,
|
||||
input: Arc<InputMethod>,
|
||||
datamap: HandDatamap,
|
||||
material: OnceCell<Handle<DefaultMaterial>>,
|
||||
vis_entity: OnceCell<Entity>,
|
||||
hand_tracker: openxr::HandTracker,
|
||||
capture_manager: CaptureManager,
|
||||
}
|
||||
impl SkHand {
|
||||
fn compare_distance(&self, field: &Field) -> f32 {
|
||||
let InputDataType::Hand(hand) = &*self.input.data.lock() else {
|
||||
return f32::INFINITY;
|
||||
};
|
||||
let spatial = &self.input.spatial;
|
||||
let thumb_tip_distance = field.distance(spatial, hand.thumb.tip.position.into());
|
||||
let index_tip_distance = field.distance(spatial, hand.index.tip.position.into());
|
||||
let middle_tip_distance = field.distance(spatial, hand.middle.tip.position.into());
|
||||
let ring_tip_distance = field.distance(spatial, hand.ring.tip.position.into());
|
||||
|
||||
(thumb_tip_distance * 0.3)
|
||||
+ (index_tip_distance * 0.4)
|
||||
+ (middle_tip_distance * 0.15)
|
||||
+ (ring_tip_distance * 0.15)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,25 +7,26 @@ use crate::{
|
||||
spatial::{Spatial, EXPORTED_SPATIALS},
|
||||
Node, OwnedNode,
|
||||
},
|
||||
TOKIO,
|
||||
};
|
||||
use bevy::prelude::Resource;
|
||||
use bevy_mod_openxr::helper_traits::{ToQuat, ToVec3};
|
||||
use glam::{vec3, Mat4};
|
||||
use input::{
|
||||
eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController,
|
||||
sk_hand::SkHand,
|
||||
};
|
||||
use openxr::SpaceLocationFlags;
|
||||
use play_space::PlaySpaceBounds;
|
||||
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
use stereokit_rust::{
|
||||
sk::{DisplayMode, MainThreadToken, Sk},
|
||||
system::{Handed, Input, Key, World},
|
||||
util::Device,
|
||||
};
|
||||
use tracing::info;
|
||||
use zbus::{interface, object_server::Interface, zvariant::OwnedObjectPath, Connection};
|
||||
|
||||
pub mod input;
|
||||
pub mod play_space;
|
||||
|
||||
enum Inputs {
|
||||
pub(crate) enum Inputs {
|
||||
XR {
|
||||
controller_left: SkController,
|
||||
controller_right: SkController,
|
||||
@@ -41,39 +42,54 @@ enum Inputs {
|
||||
},
|
||||
}
|
||||
|
||||
struct ControllerInput {
|
||||
pose: openxr::Space,
|
||||
select: openxr::Action<f32>,
|
||||
grab: openxr::Action<f32>,
|
||||
scroll: openxr::Action<openxr::Vector2f>,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct ServerObjects {
|
||||
connection: Connection,
|
||||
hmd: (Arc<Spatial>, ObjectHandle<SpatialRef>),
|
||||
play_space: Option<(Arc<Spatial>, ObjectHandle<SpatialRef>)>,
|
||||
inputs: Inputs,
|
||||
disable_controllers: bool,
|
||||
disable_hands: bool,
|
||||
inputs: Option<Inputs>,
|
||||
pub view_space: Option<openxr::Space>,
|
||||
pub ref_space: Option<openxr::Space>,
|
||||
}
|
||||
|
||||
pub struct TrackingRefs {
|
||||
view_space: openxr::Space,
|
||||
ref_space: openxr::Space,
|
||||
left_controller_space: openxr::Space,
|
||||
right_controller_space: openxr::Space,
|
||||
left_hand_tracker: Option<openxr::HandTracker>,
|
||||
right_hand_tracker: Option<openxr::HandTracker>,
|
||||
}
|
||||
|
||||
impl ServerObjects {
|
||||
pub fn new(
|
||||
connection: Connection,
|
||||
sk: &Sk,
|
||||
disable_controllers: bool,
|
||||
disable_hands: bool,
|
||||
) -> ServerObjects {
|
||||
pub fn new(connection: Connection) -> ServerObjects {
|
||||
let hmd = SpatialRef::create(&connection, "/org/stardustxr/HMD");
|
||||
|
||||
let play_space = (World::has_bounds()
|
||||
&& World::get_bounds_size().x != 0.0
|
||||
&& World::get_bounds_size().y != 0.0)
|
||||
.then(|| SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
|
||||
if play_space.is_some() {
|
||||
let dbus_connection = connection.clone();
|
||||
tokio::task::spawn(async move {
|
||||
PlaySpaceBounds::create(&dbus_connection).await;
|
||||
dbus_connection
|
||||
.request_name("org.stardustxr.PlaySpace")
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
// TODO: implement in bevy_mod_openxr
|
||||
// let play_space = (World::has_bounds()
|
||||
// && World::get_bounds_size().x != 0.0
|
||||
// && World::get_bounds_size().y != 0.0)
|
||||
// .then(|| SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
|
||||
// let play_space = None;
|
||||
// if play_space.is_some() {
|
||||
// let dbus_connection = connection.clone();
|
||||
// TOKIO.spawn(async move {
|
||||
// PlaySpaceBounds::create(&dbus_connection).await;
|
||||
// dbus_connection
|
||||
// .request_name("org.stardustxr.PlaySpace")
|
||||
// .await
|
||||
// .unwrap();
|
||||
// });
|
||||
// }
|
||||
|
||||
tokio::task::spawn({
|
||||
TOKIO.spawn({
|
||||
let connection = connection.clone();
|
||||
async move {
|
||||
connection
|
||||
@@ -87,100 +103,124 @@ impl ServerObjects {
|
||||
}
|
||||
});
|
||||
|
||||
let inputs = if sk.get_active_display_mode() == DisplayMode::MixedReality {
|
||||
Inputs::XR {
|
||||
controller_left: SkController::new(&connection, Handed::Left).unwrap(),
|
||||
controller_right: SkController::new(&connection, Handed::Right).unwrap(),
|
||||
hand_left: SkHand::new(&connection, Handed::Left).unwrap(),
|
||||
hand_right: SkHand::new(&connection, Handed::Right).unwrap(),
|
||||
eye_pointer: Device::has_eye_gaze()
|
||||
.then(EyePointer::new)
|
||||
.transpose()
|
||||
.unwrap(),
|
||||
}
|
||||
} else {
|
||||
Inputs::MousePointer(MousePointer::new().unwrap())
|
||||
};
|
||||
// let inputs = if sk.get_active_display_mode() == DisplayMode::MixedReality {
|
||||
// Inputs::XR {
|
||||
// controller_left: SkController::new(&connection, Handed::Left).unwrap(),
|
||||
// controller_right: SkController::new(&connection, Handed::Right).unwrap(),
|
||||
// hand_left: SkHand::new(&connection, Handed::Left).unwrap(),
|
||||
// hand_right: SkHand::new(&connection, Handed::Right).unwrap(),
|
||||
// // TODO: implement in bevy_mod_openxr
|
||||
// eye_pointer: false.then(EyePointer::new).transpose().unwrap(),
|
||||
// }
|
||||
// } else {
|
||||
// Inputs::MousePointer(MousePointer::new().unwrap())
|
||||
// };
|
||||
|
||||
ServerObjects {
|
||||
connection,
|
||||
hmd,
|
||||
play_space,
|
||||
inputs,
|
||||
disable_controllers,
|
||||
disable_hands,
|
||||
play_space: None,
|
||||
inputs: None,
|
||||
ref_space: None,
|
||||
view_space: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
|
||||
let hmd_pose = Input::get_head();
|
||||
self.hmd
|
||||
.0
|
||||
.set_local_transform(Mat4::from_scale_rotation_translation(
|
||||
vec3(1.0, 1.0, 1.0),
|
||||
hmd_pose.orientation.into(),
|
||||
hmd_pose.position.into(),
|
||||
));
|
||||
|
||||
if let Some(play_space) = self.play_space.as_ref() {
|
||||
let pose = World::get_bounds_pose();
|
||||
play_space
|
||||
.0
|
||||
.set_local_transform(Mat4::from_rotation_translation(
|
||||
pose.orientation.into(),
|
||||
pose.position.into(),
|
||||
));
|
||||
}
|
||||
|
||||
if sk.get_active_display_mode() != DisplayMode::MixedReality {
|
||||
if Input::key(Key::F6).is_just_inactive() {
|
||||
self.inputs = Inputs::MousePointer(MousePointer::new().unwrap());
|
||||
}
|
||||
// if Input::key(Key::F7).is_just_inactive() {
|
||||
// self.inputs = Inputs::Controllers((
|
||||
// SkController::new(Handed::Left).unwrap(),
|
||||
// SkController::new(Handed::Right).unwrap(),
|
||||
// ));
|
||||
// }
|
||||
if Input::key(Key::F8).is_just_inactive() {
|
||||
self.inputs = Inputs::Hands {
|
||||
left: SkHand::new(&self.connection, Handed::Left).unwrap(),
|
||||
right: SkHand::new(&self.connection, Handed::Right).unwrap(),
|
||||
};
|
||||
pub(crate) fn update(
|
||||
&mut self,
|
||||
session: Option<&openxr::Session<openxr::AnyGraphics>>,
|
||||
time: Option<openxr::Time>,
|
||||
) {
|
||||
if let (Some(session), Some(ref_space), Some(time)) =
|
||||
(session, self.ref_space.as_ref(), time)
|
||||
{
|
||||
'hmd: {
|
||||
if let Some(view) = self.view_space.as_ref() {
|
||||
let hmd_pose = match view.locate(ref_space, time) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
tracing::error!("error while locating hmd: {err}");
|
||||
break 'hmd;
|
||||
}
|
||||
};
|
||||
if hmd_pose.location_flags.contains(
|
||||
SpaceLocationFlags::POSITION_TRACKED
|
||||
| SpaceLocationFlags::ORIENTATION_TRACKED,
|
||||
) {
|
||||
self.hmd
|
||||
.0
|
||||
.set_local_transform(Mat4::from_scale_rotation_translation(
|
||||
vec3(1.0, 1.0, 1.0),
|
||||
hmd_pose.pose.orientation.to_quat(),
|
||||
hmd_pose.pose.position.to_vec3(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if let Some(play_space) = self.play_space.as_ref() {
|
||||
// let pose = World::get_bounds_pose();
|
||||
// play_space
|
||||
// .0
|
||||
// .set_local_transform(Mat4::from_rotation_translation(
|
||||
// pose.orientation.into(),
|
||||
// pose.position.into(),
|
||||
// ));
|
||||
// }
|
||||
|
||||
// if sk.get_active_display_mode() != DisplayMode::MixedReality {
|
||||
// if Input::key(Key::F6).is_just_inactive() {
|
||||
// self.inputs = Inputs::MousePointer(MousePointer::new().unwrap());
|
||||
// }
|
||||
// // if Input::key(Key::F7).is_just_inactive() {
|
||||
// // self.inputs = Inputs::Controllers((
|
||||
// // SkController::new(Handed::Left).unwrap(),
|
||||
// // SkController::new(Handed::Right).unwrap(),
|
||||
// // ));
|
||||
// // }
|
||||
// if Input::key(Key::F8).is_just_inactive() {
|
||||
// self.inputs = Inputs::Hands {
|
||||
// left: SkHand::new(&self.connection, Handed::Left).unwrap(),
|
||||
// right: SkHand::new(&self.connection, Handed::Right).unwrap(),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
match &mut self.inputs {
|
||||
Inputs::XR {
|
||||
Some(Inputs::XR {
|
||||
controller_left,
|
||||
controller_right,
|
||||
hand_left,
|
||||
hand_right,
|
||||
eye_pointer,
|
||||
} => {
|
||||
if !self.disable_controllers {
|
||||
controller_left.update(token);
|
||||
controller_right.update(token);
|
||||
}
|
||||
if !self.disable_hands {
|
||||
hand_left.update(sk, token);
|
||||
hand_right.update(sk, token);
|
||||
}
|
||||
if let Some(eye_pointer) = eye_pointer {
|
||||
eye_pointer.update();
|
||||
}
|
||||
}) => {
|
||||
// controller_left.update(token);
|
||||
// controller_right.update(token);
|
||||
// hand_left.update(sk, token);
|
||||
// hand_right.update(sk, token);
|
||||
// if let Some(eye_pointer) = eye_pointer {
|
||||
// eye_pointer.update();
|
||||
// }
|
||||
}
|
||||
Inputs::MousePointer(mouse_pointer) => mouse_pointer.update(),
|
||||
Some(Inputs::MousePointer(mouse_pointer)) => {}
|
||||
// Inputs::Controllers((left, right)) => {
|
||||
// left.update(token);
|
||||
// right.update(token);
|
||||
// }
|
||||
Inputs::Hands { left, right } => {
|
||||
left.update(sk, token);
|
||||
right.update(sk, token);
|
||||
Some(Inputs::Hands { left, right }) => {
|
||||
// left.update(sk, token);
|
||||
// right.update(sk, token);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
pub fn set_inputs(&mut self, inputs: Inputs) {
|
||||
self.inputs = Some(inputs);
|
||||
}
|
||||
pub fn unset_inputs(&mut self, inputs: Inputs) {
|
||||
self.inputs = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ObjectHandle<I: Interface>(Connection, OwnedObjectPath, PhantomData<I>);
|
||||
@@ -188,7 +228,7 @@ impl<I: Interface> Drop for ObjectHandle<I> {
|
||||
fn drop(&mut self) {
|
||||
let connection = self.0.clone();
|
||||
let object_path = self.1.clone();
|
||||
tokio::task::spawn(async move {
|
||||
TOKIO.spawn(async move {
|
||||
connection.object_server().remove::<I, _>(object_path);
|
||||
});
|
||||
}
|
||||
@@ -202,7 +242,7 @@ impl SpatialRef {
|
||||
let uid: u64 = rand::random();
|
||||
EXPORTED_SPATIALS.lock().insert(uid, node.0.clone());
|
||||
|
||||
tokio::task::spawn({
|
||||
TOKIO.spawn({
|
||||
let connection = connection.clone();
|
||||
let path = path.to_string();
|
||||
async move {
|
||||
@@ -244,7 +284,7 @@ impl FieldRef {
|
||||
let uid: u64 = rand::random();
|
||||
EXPORTED_FIELDS.lock().insert(uid, node.0.clone());
|
||||
|
||||
tokio::task::spawn({
|
||||
TOKIO.spawn({
|
||||
let connection = connection.clone();
|
||||
let path = path.to_string();
|
||||
async move {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use stereokit_rust::system::World;
|
||||
use zbus::{interface, Connection, ObjectServer};
|
||||
|
||||
pub struct PlaySpaceBounds;
|
||||
@@ -13,14 +12,16 @@ impl PlaySpaceBounds {
|
||||
}
|
||||
#[interface(name = "org.stardustxr.PlaySpace")]
|
||||
impl PlaySpaceBounds {
|
||||
// TODO: reimplement under bevy
|
||||
#[zbus(property)]
|
||||
fn bounds(&self) -> Vec<(f64, f64)> {
|
||||
let bounds = World::get_bounds_size();
|
||||
vec![
|
||||
((bounds.x).into(), (bounds.y).into()),
|
||||
((bounds.x).into(), (-bounds.y).into()),
|
||||
((-bounds.x).into(), (-bounds.y).into()),
|
||||
((-bounds.x).into(), (bounds.y).into()),
|
||||
]
|
||||
// let bounds = World::get_bounds_size();
|
||||
// vec![
|
||||
// ((bounds.x).into(), (bounds.y).into()),
|
||||
// ((bounds.x).into(), (-bounds.y).into()),
|
||||
// ((-bounds.x).into(), (-bounds.y).into()),
|
||||
// ((-bounds.x).into(), (bounds.y).into()),
|
||||
// ]
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
77
src/oxr_render_plugin.rs
Normal file
77
src/oxr_render_plugin.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use bevy::{
|
||||
app::{App, Plugin, PostUpdate},
|
||||
prelude::{resource_added, IntoSystemConfigs as _, TransformSystem},
|
||||
render::{
|
||||
extract_resource::ExtractResourcePlugin, pipelined_rendering::PipelinedRenderingPlugin,
|
||||
Render, RenderApp,
|
||||
},
|
||||
};
|
||||
use bevy_mod_openxr::{
|
||||
init::should_run_frame_loop,
|
||||
layer_builder::ProjectionLayer,
|
||||
render::{
|
||||
begin_frame, clean_views, end_frame, init_views, insert_texture_views, locate_views,
|
||||
release_image, update_views, update_views_render_world, wait_image,
|
||||
},
|
||||
resources::{
|
||||
OxrFrameState, OxrGraphicsInfo, OxrRenderLayers, OxrSwapchainImages, OxrViews, Pipelined,
|
||||
},
|
||||
session::OxrSession,
|
||||
};
|
||||
use bevy_mod_xr::session::{XrPreDestroySession, XrRenderSet, XrSessionCreated};
|
||||
|
||||
pub struct StardustOxrRenderPlugin;
|
||||
|
||||
impl Plugin for StardustOxrRenderPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
if app.is_plugin_added::<PipelinedRenderingPlugin>() {
|
||||
app.init_resource::<Pipelined>();
|
||||
}
|
||||
|
||||
app.add_plugins((
|
||||
ExtractResourcePlugin::<OxrFrameState>::default(),
|
||||
ExtractResourcePlugin::<OxrGraphicsInfo>::default(),
|
||||
ExtractResourcePlugin::<OxrSwapchainImages>::default(),
|
||||
ExtractResourcePlugin::<OxrViews>::default(),
|
||||
))
|
||||
.add_systems(XrPreDestroySession, clean_views)
|
||||
.add_systems(
|
||||
XrSessionCreated,
|
||||
init_views.run_if(resource_added::<OxrSession>),
|
||||
)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(locate_views, update_views)
|
||||
.before(TransformSystem::TransformPropagate)
|
||||
.chain()
|
||||
.run_if(should_run_frame_loop),
|
||||
)
|
||||
.init_resource::<OxrViews>();
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
|
||||
render_app
|
||||
.add_systems(XrPreDestroySession, clean_views)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
begin_frame,
|
||||
insert_texture_views,
|
||||
locate_views,
|
||||
update_views_render_world,
|
||||
wait_image,
|
||||
)
|
||||
.chain()
|
||||
.in_set(XrRenderSet::PreRender)
|
||||
.run_if(should_run_frame_loop),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
(release_image, end_frame)
|
||||
.chain()
|
||||
.run_if(should_run_frame_loop)
|
||||
.in_set(XrRenderSet::PostRender),
|
||||
)
|
||||
.insert_resource(OxrRenderLayers(vec![Box::new(ProjectionLayer)]));
|
||||
}
|
||||
}
|
||||
@@ -87,13 +87,12 @@ impl UnownedFd {
|
||||
}
|
||||
impl Drop for UnownedFd {
|
||||
fn drop(&mut self) {
|
||||
self.0.take().unwrap().into_inner().into_raw_fd();
|
||||
let _ = self.0.take().unwrap().into_inner().into_raw_fd();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Wayland {
|
||||
display: Arc<DisplayWrapper>,
|
||||
pub socket_name: Option<String>,
|
||||
join_handle: JoinHandle<Result<()>>,
|
||||
renderer: GlesRenderer,
|
||||
output: Output,
|
||||
@@ -133,7 +132,6 @@ impl Wayland {
|
||||
|
||||
Ok(Wayland {
|
||||
display,
|
||||
socket_name,
|
||||
join_handle,
|
||||
renderer,
|
||||
output,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt};
|
||||
use crate::{
|
||||
core::task,
|
||||
nodes::{
|
||||
data::KEYMAPS,
|
||||
items::panel::{Backend, Geometry, PanelItem},
|
||||
},
|
||||
nodes::items::panel::{Backend, Geometry, PanelItem, KEYMAPS},
|
||||
};
|
||||
use mint::Vector2;
|
||||
use parking_lot::Mutex;
|
||||
@@ -203,7 +200,7 @@ impl SeatWrapper {
|
||||
pointer.frame(&mut state);
|
||||
}
|
||||
|
||||
pub fn keyboard_keys(&self, surface: WlSurface, keymap_id: u64, keys: Vec<i32>) {
|
||||
pub fn keyboard_key(&self, surface: WlSurface, keymap_id: u64, key: u32, pressed: bool) {
|
||||
let Some(state) = self.wayland_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
@@ -226,20 +223,18 @@ impl SeatWrapper {
|
||||
{
|
||||
return;
|
||||
}
|
||||
for key in keys {
|
||||
keyboard.input(
|
||||
&mut state.lock(),
|
||||
key.unsigned_abs(),
|
||||
if key > 0 {
|
||||
KeyState::Pressed
|
||||
} else {
|
||||
KeyState::Released
|
||||
},
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
0,
|
||||
|_, _, _| FilterResult::Forward::<()>,
|
||||
);
|
||||
}
|
||||
keyboard.input(
|
||||
&mut state.lock(),
|
||||
key,
|
||||
if pressed {
|
||||
KeyState::Pressed
|
||||
} else {
|
||||
KeyState::Released
|
||||
},
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
0,
|
||||
|_, _, _| FilterResult::Forward::<()>,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {
|
||||
|
||||
@@ -4,13 +4,16 @@ use super::{
|
||||
surface::CoreSurface,
|
||||
utils::*,
|
||||
};
|
||||
use crate::nodes::{
|
||||
drawable::model::ModelPart,
|
||||
items::panel::{
|
||||
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
|
||||
use crate::{
|
||||
core::error::Result,
|
||||
nodes::{
|
||||
drawable::model::ModelPart,
|
||||
items::panel::{
|
||||
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
|
||||
},
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use color_eyre::eyre::eyre;
|
||||
use mint::Vector2;
|
||||
use parking_lot::Mutex;
|
||||
use rand::Rng;
|
||||
@@ -515,11 +518,11 @@ impl Backend for XdgBackend {
|
||||
self.seat.pointer_scroll(scroll_distance, scroll_steps)
|
||||
}
|
||||
|
||||
fn keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec<i32>) {
|
||||
fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool) {
|
||||
let Some(surface) = self.wl_surface_from_id(surface) else {
|
||||
return;
|
||||
};
|
||||
self.seat.keyboard_keys(surface, keymap_id, keys)
|
||||
self.seat.keyboard_key(surface, keymap_id, key, pressed)
|
||||
}
|
||||
|
||||
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>) {
|
||||
|
||||
Reference in New Issue
Block a user